View Javadoc

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 java.lang.reflect.Array;
23  import java.lang.reflect.Method;
24  import java.util.Collection;
25  import java.util.Enumeration;
26  import java.util.Iterator;
27  import java.util.Map;
28  
29  import org.apache.velocity.exception.VelocityException;
30  import org.apache.velocity.runtime.RuntimeLogger;
31  import org.apache.velocity.runtime.log.Log;
32  import org.apache.velocity.runtime.log.RuntimeLoggerLog;
33  import org.apache.velocity.runtime.parser.node.AbstractExecutor;
34  import org.apache.velocity.runtime.parser.node.BooleanPropertyExecutor;
35  import org.apache.velocity.runtime.parser.node.GetExecutor;
36  import org.apache.velocity.runtime.parser.node.MapGetExecutor;
37  import org.apache.velocity.runtime.parser.node.MapSetExecutor;
38  import org.apache.velocity.runtime.parser.node.PropertyExecutor;
39  import org.apache.velocity.runtime.parser.node.PutExecutor;
40  import org.apache.velocity.runtime.parser.node.SetExecutor;
41  import org.apache.velocity.runtime.parser.node.SetPropertyExecutor;
42  import org.apache.velocity.util.ArrayIterator;
43  import org.apache.velocity.util.ArrayListWrapper;
44  import org.apache.velocity.util.EnumerationIterator;
45  
46  /**
47   *  Implementation of Uberspect to provide the default introspective
48   *  functionality of Velocity
49   *
50   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
51   * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
52   * @version $Id: UberspectImpl.java 898032 2010-01-11 19:51:03Z nbubna $
53   */
54  public class UberspectImpl implements Uberspect, UberspectLoggable
55  {
56      /**
57       *  Our runtime logger.
58       */
59      protected Log log;
60  
61      /**
62       *  the default Velocity introspector
63       */
64      protected Introspector introspector;
65  
66      /**
67       *  init - generates the Introspector. As the setup code
68       *  makes sure that the log gets set before this is called,
69       *  we can initialize the Introspector using the log object.
70       */
71      public void init()
72      {
73          introspector = new Introspector(log);
74      }
75  
76      /**
77       *  Sets the runtime logger - this must be called before anything
78       *  else.
79       *
80       * @param log The logger instance to use.
81       * @since 1.5
82       */
83      public void setLog(Log log)
84      {
85          this.log = log;
86      }
87  
88      /**
89       * @param runtimeLogger
90       * @deprecated Use setLog(Log log) instead.
91       */
92      public void setRuntimeLogger(RuntimeLogger runtimeLogger)
93      {
94          // in the off chance anyone still uses this method
95          // directly, use this hack to keep it working
96          setLog(new RuntimeLoggerLog(runtimeLogger));
97      }
98  
99      /**
100      *  To support iterative objects used in a <code>#foreach()</code>
101      *  loop.
102      *
103      * @param obj The iterative object.
104      * @param i Info about the object's location.
105      * @return An {@link Iterator} object.
106      */
107     public Iterator getIterator(Object obj, Info i)
108         throws Exception
109     {
110         if (obj.getClass().isArray())
111         {
112             return new ArrayIterator(obj);
113         }
114         else if (obj instanceof Collection)
115         {
116             return ((Collection) obj).iterator();
117         }
118         else if (obj instanceof Map)
119         {
120             return ((Map) obj).values().iterator();
121         }
122         else if (obj instanceof Iterator)
123         {
124             if (log.isDebugEnabled())
125             {
126                 log.debug("The iterative object in the #foreach() loop at " +
127                            i + " is of type java.util.Iterator.  Because " +
128                            "it is not resettable, if used in more than once it " +
129                            "may lead to unexpected results.");
130             }
131             return ((Iterator) obj);
132         }
133         else if (obj instanceof Enumeration)
134         {
135             if (log.isDebugEnabled())
136             {
137                 log.debug("The iterative object in the #foreach() loop at " +
138                            i + " is of type java.util.Enumeration.  Because " +
139                            "it is not resettable, if used in more than once it " +
140                            "may lead to unexpected results.");
141             }
142             return new EnumerationIterator((Enumeration) obj);
143         }
144         else
145         {
146             // look for an iterator() method to support the JDK5 Iterable
147             // interface or any user tools/DTOs that want to work in
148             // foreach without implementing the Collection interface
149             Class type = obj.getClass();
150             try
151             {
152                 Method iter = type.getMethod("iterator", null);
153                 Class returns = iter.getReturnType();
154                 if (Iterator.class.isAssignableFrom(returns))
155                 {
156                     try
157                     {
158                         return (Iterator)iter.invoke(obj, null);
159                     } 
160                     catch (Exception e)
161                     {
162                         throw new VelocityException("Error invoking the method 'iterator' on class '" 
163                             + obj.getClass().getName() +"'", e);
164                     }
165                 }
166                 else
167                 {
168                     log.debug("iterator() method of reference in #foreach loop at "
169                               + i + " does not return a true Iterator.");
170                 }
171             }
172             catch (NoSuchMethodException nsme)
173             {
174                 // eat this one, but let all other exceptions thru
175             }
176         }
177 
178         /*  we have no clue what this is  */
179         log.debug("Could not determine type of iterator in #foreach loop at " + i);
180 
181         return null;
182     }
183 
184     /**
185      *  Method
186      * @param obj
187      * @param methodName
188      * @param args
189      * @param i
190      * @return A Velocity Method.
191      */
192     public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
193         throws Exception
194     {
195         if (obj == null)
196         {
197             return null;
198         }
199 
200         Method m = introspector.getMethod(obj.getClass(), methodName, args);
201         if (m != null)
202         {
203             return new VelMethodImpl(m);
204         }
205 
206         Class cls = obj.getClass();
207         // if it's an array
208         if (cls.isArray())
209         {
210             // check for support via our array->list wrapper
211             m = introspector.getMethod(ArrayListWrapper.class, methodName, args);
212             if (m != null)
213             {
214                 // and create a method that knows to wrap the value
215                 // before invoking the method
216                 return new VelMethodImpl(m, true);
217             }
218         }
219         // watch for classes, to allow calling their static methods (VELOCITY-102)
220         else if (cls == Class.class)
221         {
222             m = introspector.getMethod((Class)obj, methodName, args);
223             if (m != null)
224             {
225                 return new VelMethodImpl(m);
226             }
227         }
228         return null;
229     }
230 
231     /**
232      * Property  getter
233      * @param obj
234      * @param identifier
235      * @param i
236      * @return A Velocity Getter Method.
237      * @throws Exception
238      */
239     public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
240         throws Exception
241     {
242         if (obj == null)
243         {
244             return null;
245         }
246 
247         Class claz = obj.getClass();
248 
249         /*
250          *  first try for a getFoo() type of property
251          *  (also getfoo() )
252          */
253         AbstractExecutor executor = new PropertyExecutor(log, introspector, claz, identifier);
254 
255         /*
256          * Let's see if we are a map...
257          */
258         if (!executor.isAlive()) 
259         {
260             executor = new MapGetExecutor(log, claz, identifier);
261         }
262 
263         /*
264          *  if that didn't work, look for get("foo")
265          */
266 
267         if (!executor.isAlive())
268         {
269             executor = new GetExecutor(log, introspector, claz, identifier);
270         }
271 
272         /*
273          *  finally, look for boolean isFoo()
274          */
275 
276         if (!executor.isAlive())
277         {
278             executor = new BooleanPropertyExecutor(log, introspector, claz,
279                                                    identifier);
280         }
281 
282         return (executor.isAlive()) ? new VelGetterImpl(executor) : null;
283     }
284 
285     /**
286      * Property setter
287      * @param obj
288      * @param identifier
289      * @param arg
290      * @param i
291      * @return A Velocity Setter method.
292      * @throws Exception
293      */
294     public VelPropertySet getPropertySet(Object obj, String identifier,
295                                          Object arg, Info i) throws Exception
296     {
297         if (obj == null)
298         {
299             return null;
300         }
301 
302         Class claz = obj.getClass();
303 
304         /*
305          *  first try for a setFoo() type of property
306          *  (also setfoo() )
307          */
308         SetExecutor executor = new SetPropertyExecutor(log, introspector, claz, identifier, arg);
309 
310         /*
311          * Let's see if we are a map...
312          */
313         if (!executor.isAlive())  {
314             executor = new MapSetExecutor(log, claz, identifier);
315         }
316 
317         /*
318          *  if that didn't work, look for put("foo", arg)
319          */
320 
321         if (!executor.isAlive())
322         {
323             executor = new PutExecutor(log, introspector, claz, arg, identifier);
324         }
325 
326         return (executor.isAlive()) ? new VelSetterImpl(executor) : null;
327     }
328 
329     /**
330      *  Implementation of VelMethod
331      */
332     public static class VelMethodImpl implements VelMethod
333     {
334         final Method method;
335         Boolean isVarArg;
336         boolean wrapArray;
337 
338         /**
339          * @param m
340          */
341         public VelMethodImpl(Method m)
342         {
343             this(m, false);
344         }
345 
346         /**
347          * @since 1.6
348          */
349         public VelMethodImpl(Method method, boolean wrapArray)
350         {
351             this.method = method;
352             this.wrapArray = wrapArray;
353         }
354 
355         private VelMethodImpl()
356         {
357             method = null;
358         }
359 
360         /**
361          * @see VelMethod#invoke(java.lang.Object, java.lang.Object[])
362          */
363         public Object invoke(Object o, Object[] actual)
364             throws Exception
365         {
366             // if we're pretending an array is a list...
367             if (wrapArray)
368             {
369                 o = new ArrayListWrapper(o);
370             }
371 
372             if (isVarArg())
373             {
374                 Class[] formal = method.getParameterTypes();
375                 int index = formal.length - 1;
376                 if (actual.length >= index)
377                 {
378                     Class type = formal[index].getComponentType();
379                     actual = handleVarArg(type, index, actual);
380                 }
381             }
382 
383             // call extension point invocation
384             return doInvoke(o, actual);
385         }
386 
387         /**
388          * Offers an extension point for subclasses (in alternate Uberspects)
389          * to alter the invocation after any array wrapping or varargs handling
390          * has already been completed.
391          * @since 1.6
392          */
393         protected Object doInvoke(Object o, Object[] actual) throws Exception
394         {
395             return method.invoke(o, actual);
396         }
397 
398         /**
399          * @return true if this method can accept a variable number of arguments
400          * @since 1.6
401          */
402         public boolean isVarArg()
403         {
404             if (isVarArg == null)
405             {
406                 Class[] formal = method.getParameterTypes();
407                 if (formal == null || formal.length == 0)
408                 {
409                     this.isVarArg = Boolean.FALSE;
410                 }
411                 else
412                 {
413                     Class last = formal[formal.length - 1];
414                     // if the last arg is an array, then
415                     // we consider this a varargs method
416                     this.isVarArg = Boolean.valueOf(last.isArray());
417                 }
418             }
419             return isVarArg.booleanValue();
420         }
421 
422         /**
423          * @param type The vararg class type (aka component type
424          *             of the expected array arg)
425          * @param index The index of the vararg in the method declaration
426          *              (This will always be one less than the number of
427          *               expected arguments.)
428          * @param actual The actual parameters being passed to this method
429          * @returns The actual parameters adjusted for the varargs in order
430          *          to fit the method declaration.
431          */
432         private Object[] handleVarArg(final Class type,
433                                       final int index,
434                                       Object[] actual)
435         {
436             // if no values are being passed into the vararg
437             if (actual.length == index)
438             {
439                 // copy existing args to new array
440                 Object[] newActual = new Object[actual.length + 1];
441                 System.arraycopy(actual, 0, newActual, 0, actual.length);
442                 // create an empty array of the expected type
443                 newActual[index] = Array.newInstance(type, 0);
444                 actual = newActual;
445             }
446             // if one value is being passed into the vararg
447             else if (actual.length == index + 1 && actual[index] != null)
448             {
449                 // make sure the last arg is an array of the expected type
450                 Class argClass = actual[index].getClass();
451                 if (!argClass.isArray() &&
452                     IntrospectionUtils.isMethodInvocationConvertible(type,
453                                                                      argClass,
454                                                                      false))
455                 {
456                     // create a 1-length array to hold and replace the last param
457                     Object lastActual = Array.newInstance(type, 1);
458                     Array.set(lastActual, 0, actual[index]);
459                     actual[index] = lastActual;
460                 }
461             }
462             // if multiple values are being passed into the vararg
463             else if (actual.length > index + 1)
464             {
465                 // put the last and extra actual in an array of the expected type
466                 int size = actual.length - index;
467                 Object lastActual = Array.newInstance(type, size);
468                 for (int i = 0; i < size; i++)
469                 {
470                     Array.set(lastActual, i, actual[index + i]);
471                 }
472 
473                 // put all into a new actual array of the appropriate size
474                 Object[] newActual = new Object[index + 1];
475                 for (int i = 0; i < index; i++)
476                 {
477                     newActual[i] = actual[i];
478                 }
479                 newActual[index] = lastActual;
480 
481                 // replace the old actual array
482                 actual = newActual;
483             }
484             return actual;
485         }
486 
487         /**
488          * @see org.apache.velocity.util.introspection.VelMethod#isCacheable()
489          */
490         public boolean isCacheable()
491         {
492             return true;
493         }
494 
495         /**
496          * @see org.apache.velocity.util.introspection.VelMethod#getMethodName()
497          */
498         public String getMethodName()
499         {
500             return method.getName();
501         }
502 
503         /**
504          * @see org.apache.velocity.util.introspection.VelMethod#getReturnType()
505          */
506         public Class getReturnType()
507         {
508             return method.getReturnType();
509         }
510     }
511 
512     /**
513      *
514      *
515      */
516     public static class VelGetterImpl implements VelPropertyGet
517     {
518         final AbstractExecutor getExecutor;
519 
520         /**
521          * @param exec
522          */
523         public VelGetterImpl(AbstractExecutor exec)
524         {
525             getExecutor = exec;
526         }
527 
528         private VelGetterImpl()
529         {
530             getExecutor = null;
531         }
532 
533         /**
534          * @see org.apache.velocity.util.introspection.VelPropertyGet#invoke(java.lang.Object)
535          */
536         public Object invoke(Object o)
537             throws Exception
538         {
539             return getExecutor.execute(o);
540         }
541 
542         /**
543          * @see org.apache.velocity.util.introspection.VelPropertyGet#isCacheable()
544          */
545         public boolean isCacheable()
546         {
547             return true;
548         }
549 
550         /**
551          * @see org.apache.velocity.util.introspection.VelPropertyGet#getMethodName()
552          */
553         public String getMethodName()
554         {
555             return getExecutor.isAlive() ? getExecutor.getMethod().getName() : null;
556         }
557     }
558 
559     /**
560      *
561      */
562     public static class VelSetterImpl implements VelPropertySet
563     {
564         private final SetExecutor setExecutor;
565 
566         /**
567          * @param setExecutor
568          */
569         public VelSetterImpl(final SetExecutor setExecutor)
570         {
571             this.setExecutor = setExecutor;
572         }
573 
574         private VelSetterImpl()
575         {
576             setExecutor = null;
577         }
578 
579         /**
580          * Invoke the found Set Executor.
581          *
582          * @param o is the Object to invoke it on.
583          * @param value in the Value to set.
584          * @return The resulting Object.
585          * @throws Exception
586          */
587         public Object invoke(final Object o, final Object value)
588             throws Exception
589         {
590             return setExecutor.execute(o, value);
591         }
592 
593         /**
594          * @see org.apache.velocity.util.introspection.VelPropertySet#isCacheable()
595          */
596         public boolean isCacheable()
597         {
598             return true;
599         }
600 
601         /**
602          * @see org.apache.velocity.util.introspection.VelPropertySet#getMethodName()
603          */
604         public String getMethodName()
605         {
606             return setExecutor.isAlive() ? setExecutor.getMethod().getName() : null;
607         }
608     }
609 }