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 }