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 }