001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.reflection;
020
021import java.lang.reflect.Constructor;
022import java.lang.reflect.Field;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025
026import lucee.commons.lang.StringUtil;
027import lucee.runtime.exp.PageException;
028import lucee.runtime.op.Caster;
029import lucee.runtime.reflection.pairs.ConstructorParameterPair;
030import lucee.runtime.reflection.pairs.MethodParameterPair;
031import lucee.runtime.type.ObjectWrap;
032import lucee.runtime.type.util.ArrayUtil;
033
034/**
035 * To invoke a Object on different ways
036 */
037public final class Invoker {
038
039    private static Method[] lastMethods;
040    private static Class lastClass;
041
042
043        /**
044         * @param clazz
045         * @param parameters
046         * @return new Instance
047         * @throws NoSuchMethodException
048         * @throws IllegalArgumentException
049         * @throws InstantiationException
050         * @throws IllegalAccessException
051         * @throws InvocationTargetException
052         */
053        public static Object newInstance(Class clazz, Object[] parameters) throws NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException  {
054                ConstructorParameterPair pair=
055                        getConstructorParameterPairIgnoreCase(clazz, parameters);
056                return pair.getConstructor().newInstance(pair.getParameters());
057        }
058        
059
060        /**
061         * search the matching constructor to defined parameter list, also translate parameters for matching
062         * @param clazz class to get constructo from
063         * @param parameters parameter for the constructor
064         * @return  Constructor parameter pair
065         * @throws NoSuchMethodException
066         */
067        public static ConstructorParameterPair getConstructorParameterPairIgnoreCase(Class clazz, Object[] parameters) throws NoSuchMethodException {
068                // set all values
069                //Class objectClass=object.getClass();
070                if(parameters==null)parameters=new Object[0];
071        
072        // set parameter classes
073                Class[] parameterClasses=new Class[parameters.length];
074                for(int i=0;i<parameters.length;i++) {
075                        parameterClasses[i]=parameters[i].getClass();
076                }
077        
078        // search right method
079                Constructor[] constructor=clazz.getConstructors();
080                for(int mode=0;mode<2;mode++) {
081                        outer: for(int i=0;i<constructor.length;i++) {
082                                Constructor c=constructor[i];
083                                
084                                
085                                Class[] paramTrg=c.getParameterTypes();
086                                // Same Parameter count
087                                if(parameterClasses.length==paramTrg.length) {
088                                        for(int y=0;y<parameterClasses.length;y++) {
089                                                if(mode==0 && parameterClasses[y]!=primitiveToWrapperType(paramTrg[y])) { 
090                                                        continue outer; 
091                                                } 
092                                                else if(mode==1) { 
093                                                        Object o=compareClasses(parameters[y], paramTrg[y]);
094                                                        if(o==null)continue outer; 
095                                                        
096                                                                parameters[y]=o;
097                                                                parameterClasses[y]=o.getClass();
098                                                        
099                                                }
100                                        }
101                                return new ConstructorParameterPair(c,parameters);
102                                }
103                                
104                        }
105                }
106                
107                // Exeception
108                String parameter="";
109                for(int i=0;i<parameterClasses.length;i++) {
110                        if(i!=0) parameter+=", ";
111                        parameter+=parameterClasses[i].getName();
112                }
113                throw new NoSuchMethodException("class constructor "+clazz.getName()+"("+parameter+") doesn't exist");
114        }
115        
116        
117        
118        
119        /**
120         * call of a method from given object 
121         * @param object object to call method from
122         * @param methodName name of the method to call 
123         * @param parameters parameter for method
124         * @return return value of the method
125         * @throws SecurityException
126         * @throws NoSuchMethodException
127         * @throws IllegalArgumentException
128         * @throws IllegalAccessException
129         * @throws InvocationTargetException
130        */
131        public static Object callMethod(Object object, String methodName, Object[] parameters) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException  {
132            MethodParameterPair pair=
133                        getMethodParameterPairIgnoreCase(object.getClass(), methodName, parameters);
134
135                                
136                return pair.getMethod().invoke(object,pair.getParameters());
137        }
138        
139        /**
140         * search the matching method to defined Method Name, also translate parameters for matching
141         * @param objectClass class object where searching method from
142         * @param methodName name of the method to search
143         * @param parameters whished parameter list
144         * @return pair with method matching and parameterlist matching
145         * @throws NoSuchMethodException
146         */
147        public static MethodParameterPair getMethodParameterPairIgnoreCase(Class objectClass, String methodName, Object[] parameters) throws NoSuchMethodException {
148                // set all values
149                        if(parameters==null)parameters=new Object[0];
150                
151                // set parameter classes
152                        Class[] parameterClasses=new Class[parameters.length];
153                        for(int i=0;i<parameters.length;i++) {
154                                parameterClasses[i]=parameters[i].getClass();
155                        }
156                                        
157                // search right method
158                        Method[] methods=null;
159                        
160                        if(lastClass!=null && lastClass.equals(objectClass)) {
161                            methods=lastMethods;
162                        }
163                        else {
164                            methods=objectClass.getDeclaredMethods();
165                        }
166                        
167                        lastClass=objectClass;
168                        lastMethods=methods;
169                        //Method[] methods=objectClass.getMethods();
170                        //Method[] methods=objectClass.getDeclaredMethods();
171                        
172                        //methods=objectClass.getDeclaredMethods();
173                        for(int mode=0;mode<2;mode++) {
174                                outer: for(int i=0;i<methods.length;i++) {
175                                        Method method=methods[i];
176                                        // Same Name
177                                        if(method.getName().equalsIgnoreCase(methodName)) {
178                                                Class[] paramTrg=method.getParameterTypes();
179                                                // Same Parameter count
180                                                if(parameterClasses.length==paramTrg.length) {
181                                                        //if(parameterClasses.length==0)return m;
182                                                        for(int y=0;y<parameterClasses.length;y++) {
183                                                                
184                                                                if(mode==0 && parameterClasses[y]!=primitiveToWrapperType(paramTrg[y])) { 
185                                                                        continue outer; 
186                                                                } 
187                                                                else if(mode==1) { 
188                                                                        Object o=compareClasses(parameters[y], paramTrg[y]);
189                                                                        
190                                                                        if(o==null) {
191                                                                            continue outer;
192                                                                        }
193                                                                        parameters[y]=o;
194                                                                        parameterClasses[y]=o.getClass();
195                                                                        
196                                                                } 
197                                                                //if(parameterClasses.length-1==y) return m;
198                                                        }
199                                                
200                                                return new MethodParameterPair(method,parameters);
201                                                }
202                                        }
203                                }
204                        }
205                        
206                        // Exeception
207        String parameter="";
208                        for(int i=0;i<parameterClasses.length;i++) {
209                                if(i!=0) parameter+=", ";
210                                parameter+=parameterClasses[i].getName();
211                        }
212                        throw new NoSuchMethodException("method "+methodName+"("+parameter+") doesn't exist in class "+objectClass.getName());
213                
214        }
215        
216        /**
217         * compare parameter with whished parameter class and convert parameter to whished type
218         * @param parameter parameter to compare
219         * @param trgClass whished type of the parameter
220         * @return converted parameter (to whished type) or null
221         */
222        private static Object compareClasses(Object parameter, Class trgClass) {
223                Class srcClass=parameter.getClass();
224                trgClass=primitiveToWrapperType(trgClass);
225                try {
226                        if(parameter instanceof ObjectWrap)
227                                parameter=((ObjectWrap)parameter).getEmbededObject();
228                
229                // parameter is already ok
230                
231                        if(srcClass==trgClass) return parameter;
232                        
233                        else if (instaceOf(srcClass,trgClass)) {
234                                return parameter;
235                        }
236                        else if (trgClass.getName().equals("java.lang.String")) {
237                                return Caster.toString(parameter);
238                        }
239                        else if (trgClass.getName().equals("java.lang.Boolean")) {
240                                return Caster.toBoolean(parameter);
241                        }
242                        else if (trgClass.getName().equals("java.lang.Byte")) {
243                                return new Byte( Caster.toString(parameter));
244                        }
245                        else if (trgClass.getName().equals("java.lang.Character")) {
246                                String str = Caster.toString(parameter);
247                                if(str.length()==1) return new Character(str.toCharArray()[0]);
248                                return null;
249                        }
250                        else if (trgClass.getName().equals("java.lang.Short")) {
251                                return Short.valueOf((short) Caster.toIntValue(parameter));
252                        }
253                        else if (trgClass.getName().equals("java.lang.Integer")) {
254                                return Integer.valueOf(Caster.toIntValue(parameter));
255                        }
256                        else if (trgClass.getName().equals("java.lang.Long")) {
257                                return Long.valueOf((long)Caster.toDoubleValue(parameter));
258                        }
259                        else if (trgClass.getName().equals("java.lang.Float")) {
260                                return Float.valueOf((float)Caster.toDoubleValue(parameter));
261                        }
262                        else if (trgClass.getName().equals("java.lang.Double")) {
263                                return Caster.toDouble(parameter);
264                        }
265                } 
266                catch (PageException e) {
267                        return null;
268                }
269                
270                return null;
271        }
272
273        /**
274         * @param srcClass
275         * @param trgClass
276         * @return is instance of or not
277         */
278        private static boolean instaceOf(Class srcClass, Class trgClass) {
279                while(srcClass!=null) {
280                    if(srcClass==trgClass) return true;
281                        srcClass=primitiveToWrapperType(srcClass.getSuperclass());
282                }
283                return false;
284        }
285
286
287        /**
288         * cast a primitive type class definition to his object reference type
289         * @param clazz class object to check and convert if it is of primitive type
290         * @return object reference class object
291         */
292        private static Class primitiveToWrapperType(Class clazz) {
293                // boolean, byte, char, short, int, long, float, and double
294                if(clazz==null) return null;
295                else if(clazz.isPrimitive()) {
296                        if(clazz.getName().equals("boolean"))return Boolean.class;
297                        else if(clazz.getName().equals("byte"))return Byte.class;
298                        else if(clazz.getName().equals("char"))return Character.class;
299                        else if(clazz.getName().equals("short"))return Short.class;
300                        else if(clazz.getName().equals("int"))return Integer.class;
301                        else if(clazz.getName().equals("long"))return Long.class;
302                        else if(clazz.getName().equals("float"))return Float.class;
303                        else if(clazz.getName().equals("double"))return Double.class;
304                }
305                return clazz;
306        }
307
308        /**
309         * to invoke a getter Method of a Object
310         * @param o Object to invoke method from
311         * @param prop Name of the Method without get
312         * @return return Value of the getter Method
313         * @throws SecurityException
314         * @throws NoSuchMethodException
315         * @throws IllegalArgumentException
316         * @throws IllegalAccessException
317         * @throws InvocationTargetException
318         */
319        public static Object callGetter(Object o, String prop) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
320                prop="get"+prop;
321                Class c=o.getClass();
322                Method m=getMethodParameterPairIgnoreCase(c, prop, null).getMethod();
323
324                //Method m=getMethodIgnoreCase(c,prop,null);
325                if(m.getReturnType().getName().equals("void"))
326                        throw new NoSuchMethodException("invalid return Type, method ["+m.getName()+"] can't have return type void");
327                return m.invoke(o,ArrayUtil.OBJECT_EMPTY);
328        }
329        
330        /**
331         * to invoke a setter Method of a Object
332         * @param o Object to invoke method from
333         * @param prop Name of the Method without get
334         * @param value Value to set to the Method
335         * @throws SecurityException
336         * @throws NoSuchMethodException
337         * @throws IllegalArgumentException
338         * @throws IllegalAccessException
339         * @throws InvocationTargetException
340         */
341        public static void callSetter(Object o, String prop,Object value) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
342                prop="set"+StringUtil.ucFirst(prop);
343                Class c=o.getClass();
344                //Class[] cArg=new Class[]{value.getClass()};
345                Object[] oArg=new Object[]{value};
346                MethodParameterPair mp = getMethodParameterPairIgnoreCase(c, prop, oArg);
347                //Method m=getMethodIgnoreCase(c,prop,cArg);
348                if(!mp.getMethod().getReturnType().getName().equals("void"))
349                        throw new NoSuchMethodException("invalid return Type, method ["+mp.getMethod().getName()+"] must have return type void, now ["+mp.getMethod().getReturnType().getName()+"]");
350                mp.getMethod().invoke(o,mp.getParameters());
351        }
352        
353        /**
354         * to get a visible Property of a object
355         * @param o Object to invoke
356         * @param prop property to call
357         * @return property value
358         * @throws NoSuchFieldException
359         * @throws IllegalArgumentException
360         * @throws IllegalAccessException
361         */
362        public static Object getProperty(Object o, String prop) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
363                Field f=getFieldIgnoreCase(o.getClass(),prop);
364                return f.get(o);
365        }
366        
367        /**
368         * assign a value to a visible property of a object
369         * @param o Object to assign value to his property
370         * @param prop name of property
371         * @param value Value to assign
372         * @throws IllegalArgumentException
373         * @throws IllegalAccessException
374         * @throws NoSuchFieldException
375         */
376        public static void setProperty(Object o, String prop,Object value) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException {
377                getFieldIgnoreCase(o.getClass(),prop).set(o,value);
378        }
379
380        /**
381         * same like method getField from Class but ignore case from field name
382         * @param c class to search the field
383         * @param name name to search
384         * @return Matching Field
385         * @throws NoSuchFieldException
386         */
387        public static Field getFieldIgnoreCase(Class c, String name) throws NoSuchFieldException  {
388                Field[] fields=c.getFields();
389                for(int i=0;i<fields.length;i++) {
390                        Field f=fields[i];
391                        // Same Name
392                        if(f.getName().equalsIgnoreCase(name)) {
393                                return f;
394                        }
395                } 
396                throw new NoSuchFieldException("Field doesn't exist");
397        }
398        
399        /**
400         * call of a static method of a Class
401         * @param staticClass class how contains method to invoke
402         * @param methodName method name to invoke
403         * @param values Arguments for the Method
404         * @return return value from method
405         * @throws PageException
406        */
407        public static Object callStaticMethod(Class staticClass, String methodName, Object[] values) throws PageException {
408                if(values==null)values=new Object[0];
409                
410                
411                
412                
413                MethodParameterPair mp;
414                try {
415                        mp = getMethodParameterPairIgnoreCase(staticClass, methodName, values);
416                } 
417                catch (NoSuchMethodException e) {
418                        throw Caster.toPageException(e);
419                }
420                
421                try {
422                        return mp.getMethod().invoke(null,mp.getParameters());
423                } 
424                catch (InvocationTargetException e) {
425                        Throwable target = e.getTargetException();
426                        if(target instanceof PageException) throw (PageException)target;
427                        throw Caster.toPageException(e.getTargetException());
428                } 
429                catch (Exception e) {
430                        throw Caster.toPageException(e);
431                }
432        }
433}
434