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 720351 2008-11-24 23:35:13Z 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                 // create an empty array of the expected type
434                 actual = new Object[] { Array.newInstance(type, 0) };
435             }
436             // if one value is being passed into the vararg
437             else if (actual.length == index + 1 && actual[index] != null)
438             {
439                 // make sure the last arg is an array of the expected type
440                 Class argClass = actual[index].getClass();
441                 if (!argClass.isArray() &&
442                     IntrospectionUtils.isMethodInvocationConvertible(type,
443                                                                      argClass,
444                                                                      false))
445                 {
446                     // create a 1-length array to hold and replace the last param
447                     Object lastActual = Array.newInstance(type, 1);
448                     Array.set(lastActual, 0, actual[index]);
449                     actual[index] = lastActual;
450                 }
451             }
452             // if multiple values are being passed into the vararg
453             else if (actual.length > index + 1)
454             {
455                 // put the last and extra actual in an array of the expected type
456                 int size = actual.length - index;
457                 Object lastActual = Array.newInstance(type, size);
458                 for (int i = 0; i < size; i++)
459                 {
460                     Array.set(lastActual, i, actual[index + i]);
461                 }
462 
463                 // put all into a new actual array of the appropriate size
464                 Object[] newActual = new Object[index + 1];
465                 for (int i = 0; i < index; i++)
466                 {
467                     newActual[i] = actual[i];
468                 }
469                 newActual[index] = lastActual;
470 
471                 // replace the old actual array
472                 actual = newActual;
473             }
474             return actual;
475         }
476 
477         /**
478          * @see org.apache.velocity.util.introspection.VelMethod#isCacheable()
479          */
480         public boolean isCacheable()
481         {
482             return true;
483         }
484 
485         /**
486          * @see org.apache.velocity.util.introspection.VelMethod#getMethodName()
487          */
488         public String getMethodName()
489         {
490             return method.getName();
491         }
492 
493         /**
494          * @see org.apache.velocity.util.introspection.VelMethod#getReturnType()
495          */
496         public Class getReturnType()
497         {
498             return method.getReturnType();
499         }
500     }
501 
502     /**
503      *
504      *
505      */
506     public static class VelGetterImpl implements VelPropertyGet
507     {
508         final AbstractExecutor getExecutor;
509 
510         /**
511          * @param exec
512          */
513         public VelGetterImpl(AbstractExecutor exec)
514         {
515             getExecutor = exec;
516         }
517 
518         private VelGetterImpl()
519         {
520             getExecutor = null;
521         }
522 
523         /**
524          * @see org.apache.velocity.util.introspection.VelPropertyGet#invoke(java.lang.Object)
525          */
526         public Object invoke(Object o)
527             throws Exception
528         {
529             return getExecutor.execute(o);
530         }
531 
532         /**
533          * @see org.apache.velocity.util.introspection.VelPropertyGet#isCacheable()
534          */
535         public boolean isCacheable()
536         {
537             return true;
538         }
539 
540         /**
541          * @see org.apache.velocity.util.introspection.VelPropertyGet#getMethodName()
542          */
543         public String getMethodName()
544         {
545             return getExecutor.isAlive() ? getExecutor.getMethod().getName() : null;
546         }
547     }
548 
549     /**
550      *
551      */
552     public static class VelSetterImpl implements VelPropertySet
553     {
554         private final SetExecutor setExecutor;
555 
556         /**
557          * @param setExecutor
558          */
559         public VelSetterImpl(final SetExecutor setExecutor)
560         {
561             this.setExecutor = setExecutor;
562         }
563 
564         private VelSetterImpl()
565         {
566             setExecutor = null;
567         }
568 
569         /**
570          * Invoke the found Set Executor.
571          *
572          * @param o is the Object to invoke it on.
573          * @param value in the Value to set.
574          * @return The resulting Object.
575          * @throws Exception
576          */
577         public Object invoke(final Object o, final Object value)
578             throws Exception
579         {
580             return setExecutor.execute(o, value);
581         }
582 
583         /**
584          * @see org.apache.velocity.util.introspection.VelPropertySet#isCacheable()
585          */
586         public boolean isCacheable()
587         {
588             return true;
589         }
590 
591         /**
592          * @see org.apache.velocity.util.introspection.VelPropertySet#getMethodName()
593          */
594         public String getMethodName()
595         {
596             return setExecutor.isAlive() ? setExecutor.getMethod().getName() : null;
597         }
598     }
599 }