001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.jexl3.internal.introspection; 018 019import java.lang.reflect.Array; 020import java.lang.reflect.InvocationTargetException; 021import org.apache.commons.jexl3.JexlException; 022 023/** 024 * Specialized executor to set a property in an object. 025 * @since 2.0 026 */ 027public class PropertySetExecutor extends AbstractExecutor.Set { 028 /** Index of the first character of the set{p,P}roperty. */ 029 private static final int SET_START_INDEX = 3; 030 /** The property. */ 031 protected final String property; 032 /** The property value class. */ 033 protected final Class<?> valueClass; 034 035 /** 036 * Discovers a PropertySetExecutor. 037 * <p>The method to be found should be named "set{P,p}property.</p> 038 * 039 * @param is the introspector 040 * @param clazz the class to find the get method from 041 * @param property the property name to find 042 * @param value the value to assign to the property 043 * @return the executor if found, null otherwise 044 */ 045 public static PropertySetExecutor discover(final Introspector is, 046 final Class<?> clazz, 047 final String property, 048 final Object value) { 049 if (property == null || property.isEmpty()) { 050 return null; 051 } 052 final java.lang.reflect.Method method = discoverSet(is, clazz, property, value); 053 return method != null? new PropertySetExecutor(clazz, method, property, value) : null; 054 } 055 056 /** 057 * Creates an instance. 058 * @param clazz the class the set method applies to 059 * @param method the method called through this executor 060 * @param key the key to use as 1st argument to the set method 061 * @param value the value 062 */ 063 protected PropertySetExecutor(final Class<?> clazz, 064 final java.lang.reflect.Method method, 065 final String key, 066 final Object value) { 067 super(clazz, method); 068 property = key; 069 valueClass = classOf(value); 070 } 071 072 @Override 073 public Object getTargetProperty() { 074 return property; 075 } 076 077 @Override 078 public Object invoke(final Object o, Object arg) throws IllegalAccessException, InvocationTargetException { 079 if (method != null) { 080 // handle the empty array case 081 if (isEmptyArray(arg)) { 082 // if array is empty but its component type is different from the method first parameter component type, 083 // replace argument with a new empty array instance (of the method first parameter component type) 084 final Class<?> componentType = method.getParameterTypes()[0].getComponentType(); 085 if (componentType != null && !componentType.equals(arg.getClass().getComponentType())) { 086 arg = Array.newInstance(componentType, 0); 087 } 088 } 089 method.invoke(o, arg); 090 } 091 return arg; 092 } 093 094 @Override 095 public Object tryInvoke(final Object o, final Object identifier, final Object value) { 096 if (o != null && method != null 097 // ensure method name matches the property name 098 && property.equals(castString(identifier)) 099 // object class should be same as executor's method declaring class 100 && objectClass.equals(o.getClass()) 101 // argument class should be eq 102 && valueClass.equals(classOf(value))) { 103 try { 104 return invoke(o, value); 105 } catch (IllegalAccessException | IllegalArgumentException xill) { 106 return TRY_FAILED;// fail 107 } catch (final InvocationTargetException xinvoke) { 108 throw JexlException.tryFailed(xinvoke); // throw 109 } 110 } 111 return TRY_FAILED; 112 } 113 114 /** 115 * Checks whether an argument is an empty array. 116 * @param arg the argument 117 * @return true if <code>arg</code> is an empty array 118 */ 119 private static boolean isEmptyArray(final Object arg) { 120 return (arg != null && arg.getClass().isArray() && Array.getLength(arg) == 0); 121 } 122 123 /** 124 * Discovers the method for a {@link org.apache.commons.jexl3.introspection.JexlPropertySet}. 125 * <p>The method to be found should be named "set{P,p}property. 126 * As a special case, any empty array will try to find a valid array-setting non-ambiguous method. 127 * 128 * @param is the introspector 129 * @param clazz the class to find the get method from 130 * @param property the name of the property to set 131 * @param arg the value to assign to the property 132 * @return the method if found, null otherwise 133 */ 134 private static java.lang.reflect.Method discoverSet(final Introspector is, 135 final Class<?> clazz, 136 final String property, 137 final Object arg) { 138 // first, we introspect for the set<identifier> setter method 139 final Object[] params = {arg}; 140 final StringBuilder sb = new StringBuilder("set"); 141 sb.append(property); 142 // uppercase nth char 143 final char c = sb.charAt(SET_START_INDEX); 144 sb.setCharAt(SET_START_INDEX, Character.toUpperCase(c)); 145 java.lang.reflect.Method method = is.getMethod(clazz, sb.toString(), params); 146 // lowercase nth char 147 if (method == null) { 148 sb.setCharAt(SET_START_INDEX, Character.toLowerCase(c)); 149 method = is.getMethod(clazz, sb.toString(), params); 150 // uppercase nth char, try array 151 if (method == null && isEmptyArray(arg)) { 152 sb.setCharAt(SET_START_INDEX, Character.toUpperCase(c)); 153 method = lookupSetEmptyArray(is, clazz, sb.toString()); 154 // lowercase nth char 155 if (method == null) { 156 sb.setCharAt(SET_START_INDEX, Character.toLowerCase(c)); 157 method = lookupSetEmptyArray(is, clazz, sb.toString()); 158 } 159 } 160 } 161 return method; 162 } 163 164 /** 165 * Finds an empty array property setter method by <code>methodName</code>. 166 * <p>This checks only one method with that name accepts an array as sole parameter. 167 * @param is the introspector 168 * @param clazz the class to find the get method from 169 * @param mname the method name to find 170 * @return the sole method that accepts an array as parameter 171 */ 172 private static java.lang.reflect.Method lookupSetEmptyArray(final Introspector is, final Class<?> clazz, final String mname) { 173 java.lang.reflect.Method candidate = null; 174 final java.lang.reflect.Method[] methods = is.getMethods(clazz, mname); 175 if (methods != null) { 176 for (final java.lang.reflect.Method method : methods) { 177 final Class<?>[] paramTypes = method.getParameterTypes(); 178 if (paramTypes.length == 1 && paramTypes[0].isArray()) { 179 if (candidate != null) { 180 // because the setter method is overloaded for different parameter type, 181 // return null here to report the ambiguity. 182 return null; 183 } 184 candidate = method; 185 } 186 } 187 } 188 return candidate; 189 } 190}