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 }