• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.apache.velocity.util.introspection;
2 
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21 
22 import org.apache.velocity.exception.VelocityException;
23 import org.apache.velocity.runtime.RuntimeConstants;
24 import org.apache.velocity.runtime.RuntimeServices;
25 import org.apache.velocity.runtime.parser.node.AbstractExecutor;
26 import org.apache.velocity.runtime.parser.node.BooleanPropertyExecutor;
27 import org.apache.velocity.runtime.parser.node.GetExecutor;
28 import org.apache.velocity.runtime.parser.node.MapGetExecutor;
29 import org.apache.velocity.runtime.parser.node.MapSetExecutor;
30 import org.apache.velocity.runtime.parser.node.PropertyExecutor;
31 import org.apache.velocity.runtime.parser.node.PutExecutor;
32 import org.apache.velocity.runtime.parser.node.SetExecutor;
33 import org.apache.velocity.runtime.parser.node.SetPropertyExecutor;
34 import org.apache.velocity.util.ArrayIterator;
35 import org.apache.velocity.util.ArrayListWrapper;
36 import org.apache.velocity.util.ClassUtils;
37 import org.apache.velocity.util.EnumerationIterator;
38 import org.apache.velocity.util.RuntimeServicesAware;
39 
40 import org.slf4j.Logger;
41 
42 import java.lang.reflect.Array;
43 import java.lang.reflect.InvocationTargetException;
44 import java.lang.reflect.Method;
45 import java.lang.reflect.Type;
46 import java.util.Enumeration;
47 import java.util.Iterator;
48 import java.util.Map;
49 
50 /**
51  *  Implementation of Uberspect to provide the default introspective
52  *  functionality of Velocity
53  *
54  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
55  * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
56  * @version $Id$
57  */
58 public class UberspectImpl implements Uberspect, RuntimeServicesAware
59 {
60     /**
61      *  Our runtime logger.
62      */
63     protected Logger log;
64 
65     /**
66      *  the default Velocity introspector
67      */
68     protected Introspector introspector;
69 
70     /**
71      * the conversion handler
72      */
73     protected TypeConversionHandler conversionHandler;
74 
75     /**
76      * runtime services
77      */
78     protected RuntimeServices rsvc;
79 
80     /**
81      *  init - generates the Introspector. As the setup code
82      *  makes sure that the log gets set before this is called,
83      *  we can initialize the Introspector using the log object.
84      */
85     @Override
init()86     public void init()
87     {
88         introspector = new Introspector(log, conversionHandler);
89     }
90 
getConversionHandler()91     public TypeConversionHandler getConversionHandler()
92     {
93         return conversionHandler;
94     }
95 
96     /**
97      * sets the runtime services
98      * @param rs runtime services
99      */
100     @Override
101     @SuppressWarnings("deprecation")
setRuntimeServices(RuntimeServices rs)102     public void setRuntimeServices(RuntimeServices rs)
103     {
104         rsvc = rs;
105         log = rsvc.getLog("introspection");
106 
107         Object conversionHandlerInstance = rs.getProperty(RuntimeConstants.CONVERSION_HANDLER_INSTANCE);
108         if (conversionHandlerInstance == null)
109         {
110             String conversionHandlerClass = rs.getString(RuntimeConstants.CONVERSION_HANDLER_CLASS);
111             if (conversionHandlerClass != null && !conversionHandlerClass.equals("none"))
112             {
113                 try
114                 {
115                     conversionHandlerInstance = ClassUtils.getNewInstance(conversionHandlerClass);
116                 }
117                 catch (ClassNotFoundException cnfe )
118                 {
119                     String err = "The specified class for ConversionHandler (" + conversionHandlerClass
120                         + ") does not exist or is not accessible to the current classloader.";
121                     log.error(err);
122                     throw new VelocityException(err, cnfe);
123                 }
124                 catch (InstantiationException ie)
125                 {
126                     throw new VelocityException("Could not instantiate class '" + conversionHandlerClass + "'", ie);
127                 }
128                 catch (IllegalAccessException ae)
129                 {
130                     throw new VelocityException("Cannot access class '" + conversionHandlerClass + "'", ae);
131                 }
132             }
133         }
134 
135         if (conversionHandlerInstance != null)
136         {
137             if (conversionHandlerInstance instanceof ConversionHandler)
138             {
139                 log.warn("The ConversionHandler interface is deprecated - see the TypeConversionHandler interface");
140                 final ConversionHandler ch = (ConversionHandler)conversionHandlerInstance;
141                 conversionHandler = new TypeConversionHandler()
142                 {
143                     @Override
144                     public boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
145                     {
146                         Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
147                         if (formalClass != null) return ch.isExplicitlyConvertible(formalClass, actual, possibleVarArg);
148                         else return false;
149                     }
150 
151                     @Override
152                     public Converter<?> getNeededConverter(Type formal, Class<?> actual)
153                     {
154                         Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
155                         if (formalClass != null) return ch.getNeededConverter(formalClass, actual);
156                         else return null;
157                     }
158 
159                     @Override
160                     public void addConverter(Type formal, Class<?> actual, Converter<?> converter)
161                     {
162                         Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
163                         if (formalClass != null) ch.addConverter(formalClass, actual, converter);
164                         else throw new UnsupportedOperationException("This conversion handler doesn't know how to handle Type: " + formal.getTypeName());
165                     }
166                 };
167             }
168             else if (!(conversionHandlerInstance instanceof TypeConversionHandler))
169             {
170                 String err = "The specified class or provided instance for the conversion handler (" + conversionHandlerInstance.getClass().getName()
171                         + ") does not implement " + TypeConversionHandler.class.getName()
172                         + "; Velocity is not initialized correctly.";
173 
174                 log.error(err);
175                 throw new VelocityException(err, null, rsvc.getLogContext().getStackTrace());
176             }
177             else
178             {
179                 conversionHandler = (TypeConversionHandler)conversionHandlerInstance;
180             }
181         }
182     }
183 
184     /**
185      *  Sets the runtime logger - this must be called before anything
186      *  else.
187      *
188      * @param log The logger instance to use.
189      * @since 1.5
190      * @deprecated logger is now set by default to the namespace logger "velocity.rendering".
191      */
setLog(Logger log)192     public void setLog(Logger log)
193     {
194         this.log = log;
195     }
196 
197     /**
198      *  To support iterative objects used in a <code>#foreach()</code>
199      *  loop.
200      *
201      * @param obj The iterative object.
202      * @param i Info about the object's location.
203      * @return An {@link Iterator} object.
204      */
205     @Override
getIterator(Object obj, Info i)206     public Iterator getIterator(Object obj, Info i)
207     {
208         if (obj.getClass().isArray())
209         {
210             return new ArrayIterator(obj);
211         }
212         else if (obj instanceof Iterable)
213         {
214             return ((Iterable) obj).iterator();
215         }
216         else if (obj instanceof Map)
217         {
218             return ((Map) obj).values().iterator();
219         }
220         else if (obj instanceof Iterator)
221         {
222             log.debug("The iterative object in the #foreach() loop at {}" +
223                       " is of type java.util.Iterator.  Because " +
224                       "it is not resettable, if used in more than once it " +
225                       "may lead to unexpected results.", i);
226             return ((Iterator) obj);
227         }
228         else if (obj instanceof Enumeration)
229         {
230             log.debug("The iterative object in the #foreach() loop at {}" +
231                       " is of type java.util.Enumeration.  Because " +
232                       "it is not resettable, if used in more than once it " +
233                       "may lead to unexpected results.", i);
234             return new EnumerationIterator((Enumeration) obj);
235         }
236         else
237         {
238             // look for an iterator() method to support the JDK5 Iterable
239             // interface or any user tools/DTOs that want to work in
240             // foreach without implementing the Collection interface
241             Class<?> type = obj.getClass();
242             try
243             {
244                 Method iter = type.getMethod("iterator");
245                 Class<?> returns = iter.getReturnType();
246                 if (Iterator.class.isAssignableFrom(returns))
247                 {
248                     try
249                     {
250                         return (Iterator)iter.invoke(obj);
251                     }
252                     catch (IllegalAccessException e)
253                     {
254                         // Cannot invoke this method, just give up
255                     }
256                     catch (Exception e)
257                     {
258                         throw new VelocityException("Error invoking the method 'iterator' on class '"
259                             + obj.getClass().getName() +"'", e, rsvc.getLogContext().getStackTrace());
260                     }
261                 }
262                 else
263                 {
264                     log.debug("iterator() method of reference in #foreach loop at " +
265                               "{} does not return a true Iterator.", i);
266                 }
267             }
268             catch (NoSuchMethodException nsme)
269             {
270                 // eat this one, but let all other exceptions thru
271             }
272         }
273 
274         /*  we have no clue what this is  */
275         log.debug("Could not determine type of iterator in #foreach loop at {}", i);
276 
277         return null;
278     }
279 
280     /**
281      *  Method
282      * @param obj
283      * @param methodName
284      * @param args
285      * @param i
286      * @return A Velocity Method.
287      */
288     @Override
getMethod(Object obj, String methodName, Object[] args, Info i)289     public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
290     {
291         if (obj == null)
292         {
293             return null;
294         }
295 
296         Method m = introspector.getMethod(obj.getClass(), methodName, args);
297         if (m != null)
298         {
299             return new VelMethodImpl(m, false, getNeededConverters(m.getGenericParameterTypes(), args));
300         }
301 
302         Class<?> cls = obj.getClass();
303         // if it's an array
304         if (cls.isArray())
305         {
306             // check for support via our array->list wrapper
307             m = introspector.getMethod(ArrayListWrapper.class, methodName, args);
308             if (m != null)
309             {
310                 // and create a method that knows to wrap the value
311                 // before invoking the method
312                 return new VelMethodImpl(m, true, getNeededConverters(m.getGenericParameterTypes(), args));
313             }
314         }
315         // watch for classes, to allow calling their static methods (VELOCITY-102)
316         else if (cls == Class.class)
317         {
318             m = introspector.getMethod((Class<?>)obj, methodName, args);
319             if (m != null)
320             {
321                 return new VelMethodImpl(m, false, getNeededConverters(m.getGenericParameterTypes(), args));
322             }
323         }
324         return null;
325     }
326 
327     /**
328      * get the list of needed converters to adapt passed argument types to method types
329      * @return null if not conversion needed, otherwise an array containing needed converters
330      */
getNeededConverters(Type[] expected, Object[] provided)331     private Converter<?>[] getNeededConverters(Type[] expected, Object[] provided)
332     {
333         if (conversionHandler == null) return null;
334         // var args are not handled here - CB TODO
335         int n = Math.min(expected.length, provided.length);
336         Converter<?>[] converters = null;
337         for (int i = 0; i < n; ++i)
338         {
339             Object arg = provided[i];
340             if (arg == null) continue;
341             Converter<?> converter = conversionHandler.getNeededConverter(expected[i], arg.getClass());
342             if (converter != null)
343             {
344                 if (converters == null)
345                 {
346                     converters = new Converter[expected.length];
347                 }
348                 converters[i] = converter;
349             }
350         }
351         return converters;
352     }
353 
354     /**
355      * Property  getter
356      * @param obj
357      * @param identifier
358      * @param i
359      * @return A Velocity Getter Method.
360      */
361     @Override
getPropertyGet(Object obj, String identifier, Info i)362     public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
363     {
364         if (obj == null)
365         {
366             return null;
367         }
368 
369         Class<?> claz = obj.getClass();
370 
371         /*
372          *  first try for a getFoo() type of property
373          *  (also getfoo() )
374          */
375         AbstractExecutor executor = new PropertyExecutor(log, introspector, claz, identifier);
376 
377         /*
378          * Let's see if we are a map...
379          */
380         if (!executor.isAlive())
381         {
382             executor = new MapGetExecutor(log, obj, identifier);
383         }
384 
385         /*
386          *  if that didn't work, look for get("foo")
387          */
388 
389         if (!executor.isAlive())
390         {
391             executor = new GetExecutor(log, introspector, claz, identifier);
392         }
393 
394         /*
395          *  finally, look for boolean isFoo()
396          */
397 
398         if (!executor.isAlive())
399         {
400             executor = new BooleanPropertyExecutor(log, introspector, claz,
401                     identifier);
402         }
403 
404         /*
405          * and idem on an array
406          */
407         if (!executor.isAlive() && obj.getClass().isArray())
408         {
409             executor = new BooleanPropertyExecutor(log, introspector, ArrayListWrapper.class,
410                     identifier, true);
411         }
412 
413         return (executor.isAlive()) ? new VelGetterImpl(executor) : null;
414     }
415 
416     /**
417      * Property setter
418      * @param obj
419      * @param identifier
420      * @param arg
421      * @param i
422      * @return A Velocity Setter method.
423      */
424     @Override
getPropertySet(Object obj, String identifier, Object arg, Info i)425     public VelPropertySet getPropertySet(Object obj, String identifier,
426                                          Object arg, Info i)
427     {
428         if (obj == null)
429         {
430             return null;
431         }
432 
433         Class<?> claz = obj.getClass();
434 
435         /*
436          *  first try for a setFoo() type of property
437          *  (also setfoo() )
438          */
439         SetExecutor executor = new SetPropertyExecutor(log, introspector, claz, identifier, arg);
440 
441         /*
442          * Let's see if we are a map...
443          */
444         if (!executor.isAlive())  {
445             executor = new MapSetExecutor(log, claz, identifier);
446         }
447 
448         /*
449          *  if that didn't work, look for put("foo", arg)
450          */
451 
452         if (!executor.isAlive())
453         {
454             executor = new PutExecutor(log, introspector, claz, arg, identifier);
455         }
456 
457         return (executor.isAlive()) ? new VelSetterImpl(executor) : null;
458     }
459 
460     /**
461      *  Implementation of VelMethod
462      */
463     public static class VelMethodImpl implements VelMethod
464     {
465         final Method method;
466         Boolean isVarArg;
467         boolean wrapArray;
468         Converter<?> converters[];
469 
470         /**
471          * @param m
472          */
VelMethodImpl(Method m)473         public VelMethodImpl(Method m)
474         {
475             this(m, false, null);
476         }
477 
478         /**
479          * @param method
480          * @param wrapArray
481          * @since 1.6
482          */
VelMethodImpl(Method method, boolean wrapArray)483         public VelMethodImpl(Method method, boolean wrapArray)
484         {
485             this(method, wrapArray, null);
486         }
487 
488         /**
489          * @param method
490          * @param wrapArray
491          * @param converters
492          * @since 2.0
493          */
VelMethodImpl(Method method, boolean wrapArray, Converter<?>[] converters)494         public VelMethodImpl(Method method, boolean wrapArray, Converter<?>[] converters)
495         {
496             this.method = method;
497             this.wrapArray = wrapArray;
498             this.converters = converters;
499         }
500 
VelMethodImpl()501         private VelMethodImpl()
502         {
503             method = null;
504         }
505 
506         /**
507          * @param o
508          * @param actual
509          * @return invocation result
510          * @see VelMethod#invoke(java.lang.Object, java.lang.Object[])
511          */
512         @Override
invoke(Object o, Object[] actual)513         public Object invoke(Object o, Object[] actual)
514             throws IllegalAccessException, InvocationTargetException
515         {
516             // if we're pretending an array is a list...
517             if (wrapArray)
518             {
519                 o = new ArrayListWrapper(o);
520             }
521 
522             if (isVarArg())
523             {
524                 Class<?>[] formal = method.getParameterTypes();
525                 int index = formal.length - 1;
526                 if (actual.length >= index)
527                 {
528                     Class<?> type = formal[index].getComponentType();
529                     actual = handleVarArg(type, index, actual);
530                 }
531             }
532 
533             if (converters != null)
534             {
535                 // some converters may throw an ArithmeticException
536                 // which we want to wrap into an IllegalArgumentException
537                 try
538                 {
539                     for (int i = 0; i < actual.length; ++i)
540                     {
541                         if (converters[i] != null)
542                         {
543                             actual[i] = converters[i].convert(actual[i]);
544                         }
545                     }
546                 }
547                 catch (ArithmeticException ae)
548                 {
549                     throw new IllegalArgumentException(ae);
550                 }
551             }
552 
553             // call extension point invocation
554             return doInvoke(o, actual);
555         }
556 
557         /**
558          * Offers an extension point for subclasses (in alternate Uberspects)
559          * to alter the invocation after any array wrapping or varargs handling
560          * has already been completed.
561          * @param o target object
562          * @param actual arguments
563          * @return invocation result
564          * @throws IllegalAccessException
565          * @throws InvocationTargetException
566          * @since 1.6
567          */
doInvoke(Object o, Object[] actual)568         protected Object doInvoke(Object o, Object[] actual)
569             throws IllegalAccessException, InvocationTargetException
570         {
571             return method.invoke(o, actual);
572         }
573 
574         /**
575          * @return true if this method can accept a variable number of arguments
576          * @since 1.6
577          */
isVarArg()578         public boolean isVarArg()
579         {
580             if (isVarArg == null)
581             {
582                 Class<?>[] formal = method.getParameterTypes();
583                 if (formal.length == 0)
584                 {
585                     this.isVarArg = Boolean.FALSE;
586                 }
587                 else
588                 {
589                     Class<?> last = formal[formal.length - 1];
590                     // if the last arg is an array, then
591                     // we consider this a varargs method
592                     this.isVarArg = last.isArray();
593                 }
594             }
595             return isVarArg;
596         }
597 
598         /**
599          * @param type The vararg class type (aka component type
600          *             of the expected array arg)
601          * @param index The index of the vararg in the method declaration
602          *              (This will always be one less than the number of
603          *               expected arguments.)
604          * @param actual The actual parameters being passed to this method
605          * @return The actual parameters adjusted for the varargs in order
606          *          to fit the method declaration.
607          */
handleVarArg(final Class<?> type, final int index, Object[] actual)608         private Object[] handleVarArg(final Class<?> type,
609                                       final int index,
610                                       Object[] actual)
611         {
612             // if no values are being passed into the vararg
613             if (actual.length == index)
614             {
615                 // copy existing args to new array
616                 Object[] newActual = new Object[actual.length + 1];
617                 System.arraycopy(actual, 0, newActual, 0, actual.length);
618                 // create an empty array of the expected type
619                 newActual[index] = Array.newInstance(type, 0);
620                 actual = newActual;
621             }
622             // if one value is being passed into the vararg
623             else if (actual.length == index + 1 && actual[index] != null)
624             {
625                 // make sure the last arg is an array of the expected type
626                 Class<?> argClass = actual[index].getClass();
627                 if (!argClass.isArray() && IntrospectionUtils.isMethodInvocationConvertible(type, argClass, false))
628                 {
629                     // create a 1-length array to hold and replace the last param
630                     Object lastActual = Array.newInstance(type, 1);
631                     Array.set(lastActual, 0, actual[index]);
632                     actual[index] = lastActual;
633                 }
634             }
635             // if multiple values are being passed into the vararg
636             else if (actual.length > index + 1)
637             {
638                 // put the last and extra actual in an array of the expected type
639                 int size = actual.length - index;
640                 Object lastActual = Array.newInstance(type, size);
641                 for (int i = 0; i < size; i++)
642                 {
643                     Array.set(lastActual, i, actual[index + i]);
644                 }
645 
646                 // put all into a new actual array of the appropriate size
647                 Object[] newActual = new Object[index + 1];
648                 System.arraycopy(actual, 0, newActual, 0, index);
649                 newActual[index] = lastActual;
650 
651                 // replace the old actual array
652                 actual = newActual;
653             }
654             return actual;
655         }
656 
657         /**
658          * @see org.apache.velocity.util.introspection.VelMethod#isCacheable()
659          */
660         @Override
isCacheable()661         public boolean isCacheable()
662         {
663             return true;
664         }
665 
666         /**
667          * @see org.apache.velocity.util.introspection.VelMethod#getMethodName()
668          */
669         @Override
getMethodName()670         public String getMethodName()
671         {
672             return method.getName();
673         }
674 
675         /**
676          * @see org.apache.velocity.util.introspection.VelMethod#getMethod()
677          */
678         @Override
getMethod()679         public Method getMethod()
680         {
681             return method;
682         }
683 
684         /**
685          * @see org.apache.velocity.util.introspection.VelMethod#getReturnType()
686          */
687         @Override
getReturnType()688         public Class<?> getReturnType()
689         {
690             return method.getReturnType();
691         }
692     }
693 
694     /**
695      *
696      *
697      */
698     public static class VelGetterImpl implements VelPropertyGet
699     {
700         final AbstractExecutor getExecutor;
701 
702         /**
703          * @param exec
704          */
VelGetterImpl(AbstractExecutor exec)705         public VelGetterImpl(AbstractExecutor exec)
706         {
707             getExecutor = exec;
708         }
709 
VelGetterImpl()710         private VelGetterImpl()
711         {
712             getExecutor = null;
713         }
714 
715         /**
716          * @see org.apache.velocity.util.introspection.VelPropertyGet#invoke(java.lang.Object)
717          */
718         @Override
invoke(Object o)719         public Object invoke(Object o)
720             throws IllegalAccessException, InvocationTargetException
721         {
722             return getExecutor.execute(o);
723         }
724 
725         /**
726          * @see org.apache.velocity.util.introspection.VelPropertyGet#isCacheable()
727          */
728         @Override
isCacheable()729         public boolean isCacheable()
730         {
731             return true;
732         }
733 
734         /**
735          * @see org.apache.velocity.util.introspection.VelPropertyGet#getMethodName()
736          */
737         @Override
getMethodName()738         public String getMethodName()
739         {
740             return getExecutor.isAlive() ? getExecutor.getMethod().getName() : null;
741         }
742     }
743 
744     /**
745      *
746      */
747     public static class VelSetterImpl implements VelPropertySet
748     {
749         private final SetExecutor setExecutor;
750 
751         /**
752          * @param setExecutor
753          */
VelSetterImpl(final SetExecutor setExecutor)754         public VelSetterImpl(final SetExecutor setExecutor)
755         {
756             this.setExecutor = setExecutor;
757         }
758 
VelSetterImpl()759         private VelSetterImpl()
760         {
761             setExecutor = null;
762         }
763 
764         /**
765          * Invoke the found Set Executor.
766          *
767          * @param o is the Object to invoke it on.
768          * @param value in the Value to set.
769          * @return The resulting Object.
770          */
771         @Override
invoke(final Object o, final Object value)772         public Object invoke(final Object o, final Object value)
773             throws IllegalAccessException, InvocationTargetException
774         {
775             return setExecutor.execute(o, value);
776         }
777 
778         /**
779          * @see org.apache.velocity.util.introspection.VelPropertySet#isCacheable()
780          */
781         @Override
isCacheable()782         public boolean isCacheable()
783         {
784             return true;
785         }
786 
787         /**
788          * @see org.apache.velocity.util.introspection.VelPropertySet#getMethodName()
789          */
790         @Override
getMethodName()791         public String getMethodName()
792         {
793             return setExecutor.isAlive() ? setExecutor.getMethod().getName() : null;
794         }
795     }
796 }
797