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