1 /*
2 * Copyright (c) 2003 The Visigoth Software Society. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The end-user documentation included with the redistribution, if
18 * any, must include the following acknowledgement:
19 * "This product includes software developed by the
20 * Visigoth Software Society (http://www.visigoths.org/)."
21 * Alternately, this acknowledgement may appear in the software itself,
22 * if and wherever such third-party acknowledgements normally appear.
23 *
24 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
25 * project contributors may be used to endorse or promote products derived
26 * from this software without prior written permission. For written
27 * permission, please contact visigoths@visigoths.org.
28 *
29 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
30 * nor may "FreeMarker" or "Visigoth" appear in their names
31 * without prior written permission of the Visigoth Software Society.
32 *
33 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
34 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36 * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
37 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
40 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
43 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44 * SUCH DAMAGE.
45 * ====================================================================
46 *
47 * This software consists of voluntary contributions made by many
48 * individuals on behalf of the Visigoth Software Society. For more
49 * information on the Visigoth Software Society, please see
50 * http://www.visigoths.org/
51 */
52
53 package freemarker.ext.beans;
54
55 import java.beans.BeanInfo;
56 import java.beans.IndexedPropertyDescriptor;
57 import java.beans.IntrospectionException;
58 import java.beans.Introspector;
59 import java.beans.MethodDescriptor;
60 import java.beans.PropertyDescriptor;
61 import java.io.InputStream;
62 import java.lang.reflect.AccessibleObject;
63 import java.lang.reflect.Array;
64 import java.lang.reflect.Constructor;
65 import java.lang.reflect.Field;
66 import java.lang.reflect.InvocationTargetException;
67 import java.lang.reflect.Method;
68 import java.lang.reflect.Modifier;
69 import java.math.BigDecimal;
70 import java.math.BigInteger;
71 import java.util.Arrays;
72 import java.util.Collection;
73 import java.util.Collections;
74 import java.util.Date;
75 import java.util.Enumeration;
76 import java.util.HashMap;
77 import java.util.HashSet;
78 import java.util.Iterator;
79 import java.util.List;
80 import java.util.Map;
81 import java.util.Properties;
82 import java.util.ResourceBundle;
83 import java.util.Set;
84 import java.util.StringTokenizer;
85
86 import freemarker.ext.util.IdentityHashMap;
87 import freemarker.ext.util.ModelCache;
88 import freemarker.ext.util.ModelFactory;
89 import freemarker.ext.util.WrapperTemplateModel;
90 import freemarker.log.Logger;
91 import freemarker.template.AdapterTemplateModel;
92 import freemarker.template.ObjectWrapper;
93 import freemarker.template.TemplateBooleanModel;
94 import freemarker.template.TemplateCollectionModel;
95 import freemarker.template.TemplateDateModel;
96 import freemarker.template.TemplateHashModel;
97 import freemarker.template.TemplateModel;
98 import freemarker.template.TemplateModelException;
99 import freemarker.template.TemplateNumberModel;
100 import freemarker.template.TemplateScalarModel;
101 import freemarker.template.TemplateSequenceModel;
102 import freemarker.template.utility.ClassUtil;
103 import freemarker.template.utility.Collections12;
104 import freemarker.template.utility.SecurityUtilities;
105 import freemarker.template.utility.UndeclaredThrowableException;
106
107 /**
108 * Utility class that provides generic services to reflection classes.
109 * It handles all polymorphism issues in the {@link #wrap(Object)} and {@link #unwrap(TemplateModel)} methods.
110 * @author Attila Szegedi
111 * @version $Id: BeansWrapper.java,v 1.91.2.13 2007/04/02 13:08:59 szegedia Exp $
112 */
113 public class BeansWrapper implements ObjectWrapper
114 {
115 public static final Object CAN_NOT_UNWRAP = new Object();
116 private static final Class BIGINTEGER_CLASS = java.math.BigInteger.class;
117 private static final Class BOOLEAN_CLASS = Boolean.class;
118 private static final Class CHARACTER_CLASS = Character.class;
119 private static final Class COLLECTION_CLASS = Collection.class;
120 private static final Class DATE_CLASS = Date.class;
121 private static final Class HASHADAPTER_CLASS = HashAdapter.class;
122 private static final Class ITERABLE_CLASS;
123 private static final Class LIST_CLASS = List.class;
124 private static final Class MAP_CLASS = Map.class;
125 private static final Class NUMBER_CLASS = Number.class;
126 private static final Class OBJECT_CLASS = Object.class;
127 private static final Class SEQUENCEADAPTER_CLASS = SequenceAdapter.class;
128 private static final Class SET_CLASS = Set.class;
129 private static final Class SETADAPTER_CLASS = SetAdapter.class;
130 private static final Class STRING_CLASS = String.class;
131 static {
132 Class iterable;
133 try {
134 iterable = Class.forName("java.lang.Iterable");
135 }
136 catch(ClassNotFoundException e) {
137 // We're running on a pre-1.5 JRE
138 iterable = null;
139 }
140 ITERABLE_CLASS = iterable;
141 }
142
143 // When this property is true, some things are stricter. This is mostly to
144 // catch anomalous things in development that can otherwise be valid situations
145 // for our users.
146 private static final boolean DEVELOPMENT = "true".equals(SecurityUtilities.getSystemProperty("freemarker.development"));
147
148 private static final Constructor ENUMS_MODEL_CTOR = enumsModelCtor();
149
150 private static final Logger logger = Logger.getLogger("freemarker.beans");
151
152 private static final Set UNSAFE_METHODS = createUnsafeMethodsSet();
153
154 static final Object GENERIC_GET_KEY = new Object();
155 private static final Object CONSTRUCTORS = new Object();
156 private static final Object ARGTYPES = new Object();
157
158 /**
159 * The default instance of BeansWrapper
160 */
161 private static final BeansWrapper INSTANCE = new BeansWrapper();
162
163 // Cache of hash maps that contain already discovered properties and methods
164 // for a specified class. Each key is a Class, each value is a hash map. In
165 // that hash map, each key is a property/method name, each value is a
166 // MethodDescriptor or a PropertyDescriptor assigned to that property/method.
167 private final Map classCache = new HashMap();
168 private Set cachedClassNames = new HashSet();
169
170 private final StaticModels staticModels = new StaticModels(this);
171 private final ClassBasedModelFactory enumModels = createEnumModels(this);
172
173 private final ModelCache modelCache = new BeansModelCache(this);
174
175 private final BooleanModel FALSE = new BooleanModel(Boolean.FALSE, this);
176 private final BooleanModel TRUE = new BooleanModel(Boolean.TRUE, this);
177
178 /**
179 * At this level of exposure, all methods and properties of the
180 * wrapped objects are exposed to the template.
181 */
182 public static final int EXPOSE_ALL = 0;
183
184 /**
185 * At this level of exposure, all methods and properties of the wrapped
186 * objects are exposed to the template except methods that are deemed
187 * not safe. The not safe methods are java.lang.Object methods wait() and
188 * notify(), java.lang.Class methods getClassLoader() and newInstance(),
189 * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and
190 * newInstance() methods, all java.lang.reflect.Field set methods, all
191 * java.lang.Thread and java.lang.ThreadGroup methods that can change its
192 * state, as well as the usual suspects in java.lang.System and
193 * java.lang.Runtime.
194 */
195 public static final int EXPOSE_SAFE = 1;
196
197 /**
198 * At this level of exposure, only property getters are exposed.
199 * Additionally, property getters that map to unsafe methods are not
200 * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
201 */
202 public static final int EXPOSE_PROPERTIES_ONLY = 2;
203
204 /**
205 * At this level of exposure, no bean properties and methods are exposed.
206 * Only map items, resource bundle items, and objects retrieved through
207 * the generic get method (on objects of classes that have a generic get
208 * method) can be retrieved through the hash interface. You might want to
209 * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to
210 * speed up map item retrieval.
211 */
212 public static final int EXPOSE_NOTHING = 3;
213
214 private int exposureLevel = EXPOSE_SAFE;
215 private TemplateModel nullModel = null;
216 private boolean methodsShadowItems = true;
217 private boolean exposeFields = false;
218 private int defaultDateType = TemplateDateModel.UNKNOWN;
219
220 private ObjectWrapper outerIdentity = this;
221 private boolean simpleMapWrapper;
222 private boolean strict = false;
223
224 /**
225 * Creates a new instance of BeansWrapper. The newly created instance
226 * will use the null reference as its null object, it will use
227 * {@link #EXPOSE_SAFE} method exposure level, and will not cache
228 * model instances.
229 */
230 public BeansWrapper()
231 {
232 }
233
234 /**
235 * @see #setStrict(boolean)
236 */
237 public boolean isStrict() {
238 return strict;
239 }
240
241 /**
242 * Specifies if an attempt to read a bean property that doesn't exist in the
243 * wrapped object should throw an {@link InvalidPropertyException}.
244 *
245 * <p>If this property is <tt>false</tt> (the default) then an attempt to read
246 * a missing bean property is the same as reading an existing bean property whose
247 * value is <tt>null</tt>. The template can't tell the difference, and thus always
248 * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins
249 * to handle the situation.
250 *
251 * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in
252 * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean
253 * object (as opposed to just holding <tt>null</tt> value) will cause
254 * {@link InvalidPropertyException}, which can't be suppressed in the template
255 * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way
256 * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to
257 * handle existing properties whose value is <tt>null</tt>, without the risk of
258 * hiding typos in the property names. Typos will always cause error. But mind you, it
259 * goes against the basic approach of FreeMarker, so use this feature only if you really
260 * know what are you doing.
261 */
262 public void setStrict(boolean strict) {
263 this.strict = strict;
264 }
265
266 /**
267 * When wrapping an object, the BeansWrapper commonly needs to wrap
268 * "sub-objects", for example each element in a wrapped collection.
269 * Normally it wraps these objects using itself. However, this makes
270 * it difficult to delegate to a BeansWrapper as part of a custom
271 * aggregate ObjectWrapper. This method lets you set the ObjectWrapper
272 * which will be used to wrap the sub-objects.
273 * @param outerIdentity the aggregate ObjectWrapper
274 */
275 public void setOuterIdentity(ObjectWrapper outerIdentity)
276 {
277 this.outerIdentity = outerIdentity;
278 }
279
280 /**
281 * By default returns <tt>this</tt>.
282 * @see #setOuterIdentity(ObjectWrapper)
283 */
284 public ObjectWrapper getOuterIdentity()
285 {
286 return outerIdentity;
287 }
288
289 /**
290 * By default the BeansWrapper wraps classes implementing
291 * java.util.Map using {@link MapModel}. Setting this flag will
292 * cause it to use a {@link SimpleMapModel} instead. The biggest
293 * difference is that when using a {@link SimpleMapModel}, the
294 * map will be visible as <code>TemplateHashModelEx</code>,
295 * and the subvariables will be the content of the map,
296 * without the other methods and properties of the map object.
297 * @param simpleMapWrapper enable simple map wrapping
298 */
299 public void setSimpleMapWrapper(boolean simpleMapWrapper)
300 {
301 this.simpleMapWrapper = simpleMapWrapper;
302 }
303
304 /**
305 * Tells whether Maps are exposed as simple maps, without access to their
306 * method. See {@link #setSimpleMapWrapper(boolean)} for details.
307 * @return true if Maps are exposed as simple hashes, false if they're
308 * exposed as full JavaBeans.
309 */
310 public boolean isSimpleMapWrapper()
311 {
312 return simpleMapWrapper;
313 }
314
315 /**
316 * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
317 * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
318 * constants.
319 */
320 public void setExposureLevel(int exposureLevel)
321 {
322 if(exposureLevel < EXPOSE_ALL || exposureLevel > EXPOSE_NOTHING)
323 {
324 throw new IllegalArgumentException("Illegal exposure level " + exposureLevel);
325 }
326 this.exposureLevel = exposureLevel;
327 }
328
329 int getExposureLevel()
330 {
331 return exposureLevel;
332 }
333
334 /**
335 * Controls whether public instance fields of classes are exposed to
336 * templates.
337 * @param exposeFields if set to true, public instance fields of classes
338 * that do not have a property getter defined can be accessed directly by
339 * their name. If there is a property getter for a property of the same
340 * name as the field (i.e. getter "getFoo()" and field "foo"), then
341 * referring to "foo" in template invokes the getter. If set to false, no
342 * access to public instance fields of classes is given. Default is false.
343 */
344 public void setExposeFields(boolean exposeFields)
345 {
346 this.exposeFields = exposeFields;
347 }
348
349 /**
350 * Returns whether exposure of public instance fields of classes is
351 * enabled. See {@link #setExposeFields(boolean)} for details.
352 * @return true if public instance fields are exposed, false otherwise.
353 */
354 public boolean isExposeFields()
355 {
356 return exposeFields;
357 }
358
359 /**
360 * Sets whether methods shadow items in beans. When true (this is the
361 * default value), <code>${object.name}</code> will first try to locate
362 * a bean method or property with the specified name on the object, and
363 * only if it doesn't find it will it try to call
364 * <code>object.get(name)</code>, the so-called "generic get method" that
365 * is usually used to access items of a container (i.e. elements of a map).
366 * When set to false, the lookup order is reversed and generic get method
367 * is called first, and only if it returns null is method lookup attempted.
368 */
369 public synchronized void setMethodsShadowItems(boolean methodsShadowItems)
370 {
371 this.methodsShadowItems = methodsShadowItems;
372 }
373
374 boolean isMethodsShadowItems()
375 {
376 return methodsShadowItems;
377 }
378
379 /**
380 * Sets the default date type to use for date models that result from
381 * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
382 * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is
383 * {@link TemplateDateModel#UNKNOWN}.
384 * @param defaultDateType the new default date type.
385 */
386 public synchronized void setDefaultDateType(int defaultDateType) {
387 this.defaultDateType = defaultDateType;
388 }
389
390 /**
391 * Returns the default date type. See {@link #setDefaultDateType(int)} for
392 * details.
393 * @return the default date type
394 */
395 protected int getDefaultDateType() {
396 return defaultDateType;
397 }
398
399 /**
400 * Sets whether this wrapper caches model instances. Default is false.
401 * When set to true, calling {@link #wrap(Object)} multiple times for
402 * the same object will likely return the same model (although there is
403 * no guarantee as the cache items can be cleared anytime).
404 */
405 public void setUseCache(boolean useCache)
406 {
407 modelCache.setUseCache(useCache);
408 }
409
410 /**
411 * Sets the null model. This model is returned from the
412 * {@link #wrap(Object)} method whenever the underlying object
413 * reference is null. It defaults to null reference, which is dealt
414 * with quite strictly on engine level, however you can substitute an
415 * arbitrary (perhaps more lenient) model, such as
416 * {@link freemarker.template.TemplateScalarModel#EMPTY_STRING}.
417 */
418 public void setNullModel(TemplateModel nullModel)
419 {
420 this.nullModel = nullModel;
421 }
422
423 /**
424 * Returns the default instance of the wrapper. This instance is used
425 * when you construct various bean models without explicitly specifying
426 * a wrapper. It is also returned by
427 * {@link freemarker.template.ObjectWrapper#BEANS_WRAPPER}
428 * and this is the sole instance that is used by the JSP adapter.
429 * You can modify the properties of the default instance (caching,
430 * exposure level, null model) to affect its operation. By default, the
431 * default instance is not caching, uses the <code>EXPOSE_SAFE</code>
432 * exposure level, and uses null reference as the null model.
433 */
434 public static final BeansWrapper getDefaultInstance()
435 {
436 return INSTANCE;
437 }
438
439 /**
440 * Wraps the object with a template model that is most specific for the object's
441 * class. Specifically:
442 * <ul>
443 * <li>if the object is null, returns the {@link #setNullModel(TemplateModel) null model},</li>
444 * <li>if the object is a Number returns a {@link NumberModel} for it,</li>
445 * <li>if the object is a Date returns a {@link DateModel} for it,</li>
446 * <li>if the object is a Boolean returns
447 * {@link freemarker.template.TemplateBooleanModel#TRUE} or
448 * {@link freemarker.template.TemplateBooleanModel#FALSE}</li>
449 * <li>if the object is already a TemplateModel, returns it unchanged,</li>
450 * <li>if the object is an array, returns a {@link ArrayModel} for it
451 * <li>if the object is a Map, returns a {@link MapModel} for it
452 * <li>if the object is a Collection, returns a {@link CollectionModel} for it
453 * <li>if the object is an Iterator, returns a {@link IteratorModel} for it
454 * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it
455 * <li>if the object is a String, returns a {@link StringModel} for it
456 * <li>otherwise, returns a generic {@link BeanModel} for it.
457 * </ul>
458 */
459 public TemplateModel wrap(Object object) throws TemplateModelException
460 {
461 if(object == null)
462 return nullModel;
463 return modelCache.getInstance(object);
464 }
465
466 /**
467 * @deprecated override {@link #getModelFactory(Class)} instead. Using this
468 * method will now bypass wrapper caching (if it is enabled) and always
469 * result in creation of a new wrapper. This method will be removed in 2.4
470 * @param object
471 * @param factory
472 */
473 protected TemplateModel getInstance(Object object, ModelFactory factory)
474 {
475 return factory.create(object, this);
476 }
477
478 private final ModelFactory BOOLEAN_FACTORY = new ModelFactory() {
479 public TemplateModel create(Object object, ObjectWrapper wrapper) {
480 return ((Boolean)object).booleanValue() ? TRUE : FALSE;
481 }
482 };
483
484 private static final ModelFactory ITERATOR_FACTORY = new ModelFactory() {
485 public TemplateModel create(Object object, ObjectWrapper wrapper) {
486 return new IteratorModel((Iterator)object, (BeansWrapper)wrapper);
487 }
488 };
489
490 private static final ModelFactory ENUMERATION_FACTORY = new ModelFactory() {
491 public TemplateModel create(Object object, ObjectWrapper wrapper) {
492 return new EnumerationModel((Enumeration)object, (BeansWrapper)wrapper);
493 }
494 };
495
496 protected ModelFactory getModelFactory(Class clazz) {
497 if(Map.class.isAssignableFrom(clazz)) {
498 return simpleMapWrapper ? SimpleMapModel.FACTORY : MapModel.FACTORY;
499 }
500 if(Collection.class.isAssignableFrom(clazz)) {
501 return CollectionModel.FACTORY;
502 }
503 if(Number.class.isAssignableFrom(clazz)) {
504 return NumberModel.FACTORY;
505 }
506 if(Date.class.isAssignableFrom(clazz)) {
507 return DateModel.FACTORY;
508 }
509 if(Boolean.class == clazz) { // Boolean is final
510 return BOOLEAN_FACTORY;
511 }
512 if(ResourceBundle.class.isAssignableFrom(clazz)) {
513 return ResourceBundleModel.FACTORY;
514 }
515 if(Iterator.class.isAssignableFrom(clazz)) {
516 return ITERATOR_FACTORY;
517 }
518 if(Enumeration.class.isAssignableFrom(clazz)) {
519 return ENUMERATION_FACTORY;
520 }
521 if(clazz.isArray()) {
522 return ArrayModel.FACTORY;
523 }
524 return StringModel.FACTORY;
525 }
526
527 /**
528 * Attempts to unwrap a model into underlying object. Generally, this
529 * method is the inverse of the {@link #wrap(Object)} method. In addition
530 * it will unwrap arbitrary {@link TemplateNumberModel} instances into
531 * a number, arbitrary {@link TemplateDateModel} instances into a date,
532 * {@link TemplateScalarModel} instances into a String, and
533 * {@link TemplateBooleanModel} instances into a Boolean.
534 * All other objects are returned unchanged.
535 */
536 public Object unwrap(TemplateModel model) throws TemplateModelException
537 {
538 return unwrap(model, OBJECT_CLASS);
539 }
540
541 public Object unwrap(TemplateModel model, Class hint)
542 throws TemplateModelException
543 {
544 return unwrap(model, hint, null);
545 }
546
547 private Object unwrap(TemplateModel model, Class hint, Map recursionStops)
548 throws TemplateModelException
549 {
550 if(model == nullModel) {
551 return null;
552 }
553
554 boolean isBoolean = Boolean.TYPE == hint;
555 boolean isChar = Character.TYPE == hint;
556
557 // This is for transparent interop with other wrappers (and ourselves)
558 // Passing the hint allows i.e. a Jython-aware method that declares a
559 // PyObject as its argument to receive a PyObject from a JythonModel
560 // passed as an argument to TemplateMethodModelEx etc.
561 if(model instanceof AdapterTemplateModel) {
562 Object adapted = ((AdapterTemplateModel)model).getAdaptedObject(
563 hint);
564 if(hint.isInstance(adapted)) {
565 return adapted;
566 }
567 // Attempt numeric conversion
568 if(adapted instanceof Number && ((hint.isPrimitive() && !isChar &&
569 !isBoolean) || NUMBER_CLASS.isAssignableFrom(hint))) {
570 Number number = convertUnwrappedNumber(hint,
571 (Number)adapted);
572 if(number != null) {
573 return number;
574 }
575 }
576 }
577
578 if(model instanceof WrapperTemplateModel) {
579 Object wrapped = ((WrapperTemplateModel)model).getWrappedObject();
580 if(hint.isInstance(wrapped)) {
581 return wrapped;
582 }
583 // Attempt numeric conversion
584 if(wrapped instanceof Number && ((hint.isPrimitive() && !isChar &&
585 !isBoolean) || NUMBER_CLASS.isAssignableFrom(hint))) {
586 Number number = convertUnwrappedNumber(hint,
587 (Number)wrapped);
588 if(number != null) {
589 return number;
590 }
591 }
592 }
593
594 // Translation of generic template models to POJOs. First give priority
595 // to various model interfaces based on the hint class. This helps us
596 // select the appropriate interface in multi-interface models when we
597 // know what is expected as the return type.
598
599 if(STRING_CLASS == hint) {
600 if(model instanceof TemplateScalarModel) {
601 return ((TemplateScalarModel)model).getAsString();
602 }
603 // String is final, so no other conversion will work
604 return CAN_NOT_UNWRAP;
605 }
606
607 // Primitive numeric types & Number.class and its subclasses
608 if((hint.isPrimitive() && !isChar && !isBoolean)
609 || NUMBER_CLASS.isAssignableFrom(hint)) {
610 if(model instanceof TemplateNumberModel) {
611 Number number = convertUnwrappedNumber(hint,
612 ((TemplateNumberModel)model).getAsNumber());
613 if(number != null) {
614 return number;
615 }
616 }
617 }
618
619 if(isBoolean || BOOLEAN_CLASS == hint) {
620 if(model instanceof TemplateBooleanModel) {
621 return ((TemplateBooleanModel)model).getAsBoolean()
622 ? Boolean.TRUE : Boolean.FALSE;
623 }
624 // Boolean is final, no other conversion will work
625 return CAN_NOT_UNWRAP;
626 }
627
628 if(MAP_CLASS == hint) {
629 if(model instanceof TemplateHashModel) {
630 return new HashAdapter((TemplateHashModel)model, this);
631 }
632 }
633
634 if(LIST_CLASS == hint) {
635 if(model instanceof TemplateSequenceModel) {
636 return new SequenceAdapter((TemplateSequenceModel)model, this);
637 }
638 }
639
640 if(SET_CLASS == hint) {
641 if(model instanceof TemplateCollectionModel) {
642 return new SetAdapter((TemplateCollectionModel)model, this);
643 }
644 }
645
646 if(COLLECTION_CLASS == hint
647 || ITERABLE_CLASS == hint) {
648 if(model instanceof TemplateCollectionModel) {
649 return new CollectionAdapter((TemplateCollectionModel)model,
650 this);
651 }
652 if(model instanceof TemplateSequenceModel) {
653 return new SequenceAdapter((TemplateSequenceModel)model, this);
654 }
655 }
656
657 // TemplateSequenceModels can be converted to arrays
658 if(hint.isArray()) {
659 if(model instanceof TemplateSequenceModel) {
660 if(recursionStops != null) {
661 Object retval = recursionStops.get(model);
662 if(retval != null) {
663 return retval;
664 }
665 } else {
666 recursionStops =
667 new IdentityHashMap();
668 }
669 TemplateSequenceModel seq = (TemplateSequenceModel)model;
670 Class componentType = hint.getComponentType();
671 Object array = Array.newInstance(componentType, seq.size());
672 recursionStops.put(model, array);
673 try {
674 int size = seq.size();
675 for (int i = 0; i < size; i++) {
676 Object val = unwrap(model, componentType,
677 recursionStops);
678 if(val == CAN_NOT_UNWRAP) {
679 return CAN_NOT_UNWRAP;
680 }
681 Array.set(array, i, val);
682 }
683 } finally {
684 recursionStops.remove(model);
685 }
686 return array;
687 }
688 // array classes are final, no other conversion will work
689 return CAN_NOT_UNWRAP;
690 }
691
692 // Allow one-char strings to be coerced to characters
693 if(isChar || hint == CHARACTER_CLASS) {
694 if(model instanceof TemplateScalarModel) {
695 String s = ((TemplateScalarModel)model).getAsString();
696 if(s.length() == 1) {
697 return new Character(s.charAt(0));
698 }
699 }
700 // Character is final, no other conversion will work
701 return CAN_NOT_UNWRAP;
702 }
703
704 if(DATE_CLASS.isAssignableFrom(hint)) {
705 if(model instanceof TemplateDateModel) {
706 Date date = ((TemplateDateModel)model).getAsDate();
707 if(hint.isInstance(date)) {
708 return date;
709 }
710 }
711 }
712
713 // Translation of generic template models to POJOs. Since hint was of
714 // no help initially, now use an admittedly arbitrary order of
715 // interfaces. Note we still test for isInstance and isAssignableFrom
716 // to guarantee we return a compatible value.
717 if(model instanceof TemplateNumberModel) {
718 Number number = ((TemplateNumberModel)model).getAsNumber();
719 if(hint.isInstance(number)) {
720 return number;
721 }
722 }
723 if(model instanceof TemplateDateModel) {
724 Date date = ((TemplateDateModel)model).getAsDate();
725 if(hint.isInstance(date)) {
726 return date;
727 }
728 }
729 if(model instanceof TemplateScalarModel &&
730 hint.isAssignableFrom(STRING_CLASS)) {
731 return ((TemplateScalarModel)model).getAsString();
732 }
733 if(model instanceof TemplateBooleanModel &&
734 hint.isAssignableFrom(BOOLEAN_CLASS)) {
735 return ((TemplateBooleanModel)model).getAsBoolean()
736 ? Boolean.TRUE : Boolean.FALSE;
737 }
738 if(model instanceof TemplateHashModel && hint.isAssignableFrom(
739 HASHADAPTER_CLASS)) {
740 return new HashAdapter((TemplateHashModel)model, this);
741 }
742 if(model instanceof TemplateSequenceModel
743 && hint.isAssignableFrom(SEQUENCEADAPTER_CLASS)) {
744 return new SequenceAdapter((TemplateSequenceModel)model, this);
745 }
746 if(model instanceof TemplateCollectionModel &&
747 hint.isAssignableFrom(SETADAPTER_CLASS)) {
748 return new SetAdapter((TemplateCollectionModel)model, this);
749 }
750
751 // Last ditch effort - is maybe the model itself instance of the
752 // required type?
753 if(hint.isInstance(model)) {
754 return model;
755 }
756
757 return CAN_NOT_UNWRAP;
758 }
759
760 private static Number convertUnwrappedNumber(Class hint, Number number)
761 {
762 if(hint == Integer.TYPE || hint == Integer.class) {
763 return number instanceof Integer ? (Integer)number :
764 new Integer(number.intValue());
765 }
766 if(hint == Long.TYPE || hint == Long.class) {
767 return number instanceof Long ? (Long)number :
768 new Long(number.longValue());
769 }
770 if(hint == Float.TYPE || hint == Float.class) {
771 return number instanceof Float ? (Float)number :
772 new Float(number.longValue());
773 }
774 if(hint == Double.TYPE
775 || hint == Double.class) {
776 return number instanceof Double ? (Double)number :
777 new Double(number.longValue());
778 }
779 if(hint == Byte.TYPE || hint == Byte.class) {
780 return number instanceof Byte ? (Byte)number :
781 new Byte(number.byteValue());
782 }
783 if(hint == Short.TYPE || hint == Short.class) {
784 return number instanceof Short ? (Short)number :
785 new Short(number.shortValue());
786 }
787 if(hint == BigInteger.class) {
788 return number instanceof BigInteger ? number :
789 new BigInteger(number.toString());
790 }
791 if(hint == BigDecimal.class) {
792 if(number instanceof BigDecimal) {
793 return number;
794 }
795 if(number instanceof BigInteger) {
796 return new BigDecimal((BigInteger)number);
797 }
798 if(number instanceof Long) {
799 // Because we can't represent long accurately as a
800 // double
801 return new BigDecimal(number.toString());
802 }
803 return new BigDecimal(number.doubleValue());
804 }
805 // Handle nonstandard Number subclasses as well as directly
806 // java.lang.Number too
807 if(hint.isInstance(number)) {
808 return number;
809 }
810 return null;
811 }
812
813 /**
814 * Invokes the specified method, wrapping the return value. The specialty
815 * of this method is that if the return value is null, and the return type
816 * of the invoked method is void, {@link TemplateModel#NOTHING} is returned.
817 * @param object the object to invoke the method on
818 * @param method the method to invoke
819 * @param args the arguments to the method
820 * @return the wrapped return value of the method.
821 * @throws InvocationTargetException if the invoked method threw an exception
822 * @throws IllegalAccessException if the method can't be invoked due to an
823 * access restriction.
824 * @throws TemplateModelException if the return value couldn't be wrapped
825 * (this can happen if the wrapper has an outer identity or is subclassed,
826 * and the outer identity or the subclass throws an exception. Plain
827 * BeansWrapper never throws TemplateModelException).
828 */
829 TemplateModel invokeMethod(Object object, Method method, Object[] args)
830 throws
831 InvocationTargetException,
832 IllegalAccessException,
833 TemplateModelException
834 {
835 Object retval = method.invoke(object, args);
836 return
837 method.getReturnType() == Void.TYPE
838 ? TemplateModel.NOTHING
839 : getOuterIdentity().wrap(retval);
840 }
841
842 /**
843 * Returns a hash model that represents the so-called class static models.
844 * Every class static model is itself a hash through which you can call
845 * static methods on the specified class. To obtain a static model for a
846 * class, get the element of this hash with the fully qualified class name.
847 * For example, if you place this hash model inside the root data model
848 * under name "statics", you can use i.e. <code>statics["java.lang.
849 * System"]. currentTimeMillis()</code> to call the {@link
850 * java.lang.System#currentTimeMillis()} method.
851 * @return a hash model whose keys are fully qualified class names, and
852 * that returns hash models whose elements are the static models of the
853 * classes.
854 */
855 public TemplateHashModel getStaticModels()
856 {
857 return staticModels;
858 }
859
860
861 /**
862 * Returns a hash model that represents the so-called class enum models.
863 * Every class' enum model is itself a hash through which you can access
864 * enum value declared by the specified class, assuming that class is an
865 * enumeration. To obtain an enum model for a class, get the element of this
866 * hash with the fully qualified class name. For example, if you place this
867 * hash model inside the root data model under name "enums", you can use
868 * i.e. <code>statics["java.math.RoundingMode"].UP</code> to access the
869 * {@link java.math.RoundingMode#UP} value.
870 * @return a hash model whose keys are fully qualified class names, and
871 * that returns hash models whose elements are the enum models of the
872 * classes.
873 * @throws UnsupportedOperationException if this method is invoked on a
874 * pre-1.5 JRE, as Java enums aren't supported there.
875 */
876 public TemplateHashModel getEnumModels() {
877 if(enumModels == null) {
878 throw new UnsupportedOperationException(
879 "Enums not supported on pre-1.5 JRE");
880 }
881 return enumModels;
882 }
883
884 public Object newInstance(Class clazz, List arguments)
885 throws
886 TemplateModelException
887 {
888 try
889 {
890 introspectClass(clazz);
891 Map classInfo = (Map)classCache.get(clazz);
892 Object ctors = classInfo.get(CONSTRUCTORS);
893 if(ctors == null)
894 {
895 throw new TemplateModelException("Class " + clazz.getName() +
896 " has no public constructors.");
897 }
898 Constructor ctor = null;
899 Object[] objargs;
900 if(ctors instanceof SimpleMemberModel)
901 {
902 SimpleMemberModel smm = (SimpleMemberModel)ctors;
903 ctor = (Constructor)smm.getMember();
904 objargs = smm.unwrapArguments(arguments, this);
905 }
906 else if(ctors instanceof MethodMap)
907 {
908 MethodMap methodMap = (MethodMap)ctors;
909 MemberAndArguments maa =
910 methodMap.getMemberAndArguments(arguments);
911 objargs = maa.getArgs();
912 ctor = (Constructor)maa.getMember();
913 }
914 else
915 {
916 // Cannot happen
917 throw new Error();
918 }
919 return ctor.newInstance(objargs);
920 }
921 catch (TemplateModelException e)
922 {
923 throw e;
924 }
925 catch (Exception e)
926 {
927 throw new TemplateModelException(
928 "Could not create instance of class " + clazz.getName(), e);
929 }
930 }
931
932 void introspectClass(Class clazz)
933 {
934 synchronized(classCache)
935 {
936 if(!classCache.containsKey(clazz))
937 {
938 introspectClassInternal(clazz);
939 }
940 }
941 }
942
943 private void introspectClassInternal(Class clazz)
944 {
945 String className = clazz.getName();
946 if(cachedClassNames.contains(className))
947 {
948 if(logger.isInfoEnabled())
949 {
950 logger.info("Detected a reloaded class [" + className +
951 "]. Clearing BeansWrapper caches.");
952 }
953 // Class reload detected, throw away caches
954 classCache.clear();
955 cachedClassNames = new HashSet();
956 synchronized(this)
957 {
958 modelCache.clearCache();
959 }
960 staticModels.clearCache();
961 if(enumModels != null) {
962 enumModels.clearCache();
963 }
964 }
965 classCache.put(clazz, populateClassMap(clazz));
966 cachedClassNames.add(className);
967 }
968
969 Map getClassKeyMap(Class clazz)
970 {
971 Map map;
972 synchronized(classCache)
973 {
974 map = (Map)classCache.get(clazz);
975 if(map == null)
976 {
977 introspectClassInternal(clazz);
978 map = (Map)classCache.get(clazz);
979 }
980 }
981 return map;
982 }
983
984 /**
985 * Returns the number of introspected methods/properties that should
986 * be available via the TemplateHashModel interface. Affected by the
987 * {@link #setMethodsShadowItems(boolean)} and {@link
988 * #setExposureLevel(int)} settings.
989 */
990 int keyCount(Class clazz)
991 {
992 Map map = getClassKeyMap(clazz);
993 int count = map.size();
994 if (map.containsKey(CONSTRUCTORS))
995 count--;
996 if (map.containsKey(GENERIC_GET_KEY))
997 count--;
998 if (map.containsKey(ARGTYPES))
999 count--;
1000 return count;
1001 }
1002
1003 /**
1004 * Returns the Set of names of introspected methods/properties that
1005 * should be available via the TemplateHashModel interface. Affected
1006 * by the {@link #setMethodsShadowItems(boolean)} and {@link
1007 * #setExposureLevel(int)} settings.
1008 */
1009 Set keySet(Class clazz)
1010 {
1011 Set set = new HashSet(getClassKeyMap(clazz).keySet());
1012 set.remove(CONSTRUCTORS);
1013 set.remove(GENERIC_GET_KEY);
1014 set.remove(ARGTYPES);
1015 return set;
1016 }
1017
1018 /**
1019 * Populates a map with property and method descriptors for a specified
1020 * class. If any property or method descriptors specifies a read method
1021 * that is not accessible, replaces it with appropriate accessible method
1022 * from a superclass or interface.
1023 */
1024 private Map populateClassMap(Class clazz)
1025 {
1026 // Populate first from bean info
1027 Map map = populateClassMapWithBeanInfo(clazz);
1028 // Next add constructors
1029 try
1030 {
1031 Constructor[] ctors = clazz.getConstructors();
1032 if(ctors.length == 1)
1033 {
1034 Constructor ctor = ctors[0];
1035 map.put(CONSTRUCTORS, new SimpleMemberModel(ctor, ctor.getParameterTypes()));
1036 }
1037 else if(ctors.length > 1)
1038 {
1039 MethodMap ctorMap = new MethodMap("<init>", this);
1040 for (int i = 0; i < ctors.length; i++)
1041 {
1042 ctorMap.addMember(ctors[i]);
1043 }
1044 map.put(CONSTRUCTORS, ctorMap);
1045 }
1046 }
1047 catch(SecurityException e)
1048 {
1049 logger.warn("Canont discover constructors for class " +
1050 clazz.getName(), e);
1051 }
1052 switch(map.size())
1053 {
1054 case 0:
1055 {
1056 map = Collections12.EMPTY_MAP;
1057 break;
1058 }
1059 case 1:
1060 {
1061 Map.Entry e = (Map.Entry)map.entrySet().iterator().next();
1062 map = Collections12.singletonMap(e.getKey(), e.getValue());
1063 break;
1064 }
1065 }
1066 return map;
1067 }
1068
1069 private Map populateClassMapWithBeanInfo(Class clazz)
1070 {
1071 Map classMap = new HashMap();
1072 if(exposeFields)
1073 {
1074 Field[] fields = clazz.getFields();
1075 for (int i = 0; i < fields.length; i++)
1076 {
1077 Field field = fields[i];
1078 if((field.getModifiers() & Modifier.STATIC) == 0)
1079 {
1080 classMap.put(field.getName(), field);
1081 }
1082 }
1083 }
1084 Map accessibleMethods = discoverAccessibleMethods(clazz);
1085 Method genericGet = (Method)accessibleMethods.get(MethodSignature.GET_STRING_SIGNATURE);
1086 if(genericGet == null)
1087 {
1088 genericGet = (Method)accessibleMethods.get(MethodSignature.GET_OBJECT_SIGNATURE);
1089 }
1090 if(genericGet != null)
1091 {
1092 classMap.put(GENERIC_GET_KEY, genericGet);
1093 }
1094 if(exposureLevel == EXPOSE_NOTHING)
1095 {
1096 return classMap;
1097 }
1098
1099 try
1100 {
1101 BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
1102 PropertyDescriptor[] pda = beanInfo.getPropertyDescriptors();
1103 MethodDescriptor[] mda = beanInfo.getMethodDescriptors();
1104
1105 for(int i = pda.length - 1; i >= 0; --i) {
1106 PropertyDescriptor pd = pda[i];
1107 if(pd instanceof IndexedPropertyDescriptor) {
1108 IndexedPropertyDescriptor ipd =
1109 (IndexedPropertyDescriptor)pd;
1110 Method readMethod = ipd.getIndexedReadMethod();
1111 Method publicReadMethod = getAccessibleMethod(readMethod,
1112 accessibleMethods);
1113 if(publicReadMethod != null && isSafeMethod(publicReadMethod)) {
1114 try {
1115 if(readMethod != publicReadMethod) {
1116 ipd = new IndexedPropertyDescriptor(
1117 ipd.getName(), ipd.getReadMethod(),
1118 ipd.getWriteMethod(), publicReadMethod,
1119 ipd.getIndexedWriteMethod());
1120 }
1121 classMap.put(ipd.getName(), ipd);
1122 getArgTypes(classMap).put(publicReadMethod,
1123 publicReadMethod.getParameterTypes());
1124 }
1125 catch(IntrospectionException e) {
1126 logger.warn("Couldn't properly perform introspection", e);
1127 }
1128 }
1129 }
1130 else {
1131 Method readMethod = pd.getReadMethod();
1132 Method publicReadMethod = getAccessibleMethod(readMethod, accessibleMethods);
1133 if(publicReadMethod != null && isSafeMethod(publicReadMethod)) {
1134 try {
1135 if(readMethod != publicReadMethod) {
1136 pd = new PropertyDescriptor(pd.getName(),
1137 publicReadMethod, pd.getWriteMethod());
1138 pd.setReadMethod(publicReadMethod);
1139 }
1140 classMap.put(pd.getName(), pd);
1141 }
1142 catch(IntrospectionException e)
1143 {
1144 logger.warn("Couldn't properly perform introspection", e);
1145 }
1146 }
1147 }
1148 }
1149 if(exposureLevel < EXPOSE_PROPERTIES_ONLY)
1150 {
1151 for(int i = mda.length - 1; i >= 0; --i)
1152 {
1153 MethodDescriptor md = mda[i];
1154 Method method = md.getMethod();
1155 Method publicMethod = getAccessibleMethod(method, accessibleMethods);
1156 if(publicMethod != null && isSafeMethod(publicMethod))
1157 {
1158 String name = md.getName();
1159 Object previous = classMap.get(name);
1160 if(previous instanceof Method)
1161 {
1162 // Overloaded method - replace method with a method map
1163 MethodMap methodMap = new MethodMap(name, this);
1164 methodMap.addMember((Method)previous);
1165 methodMap.addMember(publicMethod);
1166 classMap.put(name, methodMap);
1167 // remove parameter type information
1168 getArgTypes(classMap).remove(previous);
1169 }
1170 else if(previous instanceof MethodMap)
1171 {
1172 // Already overloaded method - add new overload
1173 ((MethodMap)previous).addMember(publicMethod);
1174 }
1175 else
1176 {
1177 // Simple method (this far)
1178 classMap.put(name, publicMethod);
1179 getArgTypes(classMap).put(publicMethod,
1180 publicMethod.getParameterTypes());
1181 }
1182 }
1183 }
1184 }
1185 return classMap;
1186 }
1187 catch(IntrospectionException e)
1188 {
1189 logger.warn("Couldn't properly perform introspection", e);
1190 return new HashMap();
1191 }
1192 }
1193
1194 private static Map getArgTypes(Map classMap) {
1195 Map argTypes = (Map)classMap.get(ARGTYPES);
1196 if(argTypes == null) {
1197 argTypes = new HashMap();
1198 classMap.put(ARGTYPES, argTypes);
1199 }
1200 return argTypes;
1201 }
1202
1203 static Class[] getArgTypes(Map classMap, AccessibleObject methodOrCtor) {
1204 return (Class[])((Map)classMap.get(ARGTYPES)).get(methodOrCtor);
1205 }
1206
1207 private static Method getAccessibleMethod(Method m, Map accessibles)
1208 {
1209 return m == null ? null : (Method)accessibles.get(new MethodSignature(m));
1210 }
1211
1212 boolean isSafeMethod(Method method)
1213 {
1214 return exposureLevel < EXPOSE_SAFE || !UNSAFE_METHODS.contains(method);
1215 }
1216
1217 /**
1218 * Retrieves mapping of methods to accessible methods for a class.
1219 * In case the class is not public, retrieves methods with same
1220 * signature as its public methods from public superclasses and
1221 * interfaces (if they exist). Basically upcasts every method to the
1222 * nearest accessible method.
1223 */
1224 private static Map discoverAccessibleMethods(Class clazz)
1225 {
1226 Map map = new HashMap();
1227 discoverAccessibleMethods(clazz, map);
1228 return map;
1229 }
1230
1231 private static void discoverAccessibleMethods(Class clazz, Map map)
1232 {
1233 if(Modifier.isPublic(clazz.getModifiers()))
1234 {
1235 try
1236 {
1237 Method[] methods = clazz.getMethods();
1238 for(int i = 0; i < methods.length; i++)
1239 {
1240 Method method = methods[i];
1241 MethodSignature sig = new MethodSignature(method);
1242 map.put(sig, method);
1243 }
1244 return;
1245 }
1246 catch(SecurityException e)
1247 {
1248 logger.warn("Could not discover accessible methods of class " +
1249 clazz.getName() +
1250 ", attemping superclasses/interfaces.", e);
1251 // Fall through and attempt to discover superclass/interface
1252 // methods
1253 }
1254 }
1255
1256 Class[] interfaces = clazz.getInterfaces();
1257 for(int i = 0; i < interfaces.length; i++)
1258 {
1259 discoverAccessibleMethods(interfaces[i], map);
1260 }
1261 Class superclass = clazz.getSuperclass();
1262 if(superclass != null)
1263 {
1264 discoverAccessibleMethods(superclass, map);
1265 }
1266 }
1267
1268 private static final class MethodSignature
1269 {
1270 private static final MethodSignature GET_STRING_SIGNATURE =
1271 new MethodSignature("get", new Class[] { STRING_CLASS });
1272 private static final MethodSignature GET_OBJECT_SIGNATURE =
1273 new MethodSignature("get", new Class[] { OBJECT_CLASS });
1274
1275 private final String name;
1276 private final Class[] args;
1277
1278 private MethodSignature(String name, Class[] args)
1279 {
1280 this.name = name;
1281 this.args = args;
1282 }
1283
1284 MethodSignature(Method method)
1285 {
1286 this(method.getName(), method.getParameterTypes());
1287 }
1288
1289 public boolean equals(Object o)
1290 {
1291 if(o instanceof MethodSignature)
1292 {
1293 MethodSignature ms = (MethodSignature)o;
1294 return ms.name.equals(name) && Arrays.equals(args, ms.args);
1295 }
1296 return false;
1297 }
1298
1299 public int hashCode()
1300 {
1301 return name.hashCode() ^ args.length;
1302 }
1303 }
1304
1305 private static final Set createUnsafeMethodsSet()
1306 {
1307 Properties props = new Properties();
1308 InputStream in = BeansWrapper.class.getResourceAsStream("unsafeMethods.txt");
1309 if(in != null)
1310 {
1311 String methodSpec = null;
1312 try
1313 {
1314 try
1315 {
1316 props.load(in);
1317 }
1318 finally
1319 {
1320 in.close();
1321 }
1322 Set set = new HashSet(props.size() * 4/3, .75f);
1323 Map primClasses = createPrimitiveClassesMap();
1324 for (Iterator iterator = props.keySet().iterator(); iterator.hasNext();)
1325 {
1326 methodSpec = (String) iterator.next();
1327 try {
1328 set.add(parseMethodSpec(methodSpec, primClasses));
1329 }
1330 catch(ClassNotFoundException e) {
1331 if(DEVELOPMENT) {
1332 throw e;
1333 }
1334 }
1335 catch(NoSuchMethodException e) {
1336 if(DEVELOPMENT) {
1337 throw e;
1338 }
1339 }
1340 }
1341 return set;
1342 }
1343 catch(Exception e)
1344 {
1345 throw new RuntimeException("Could not load unsafe method " + methodSpec + " " + e.getClass().getName() + " " + e.getMessage());
1346 }
1347 }
1348 return Collections.EMPTY_SET;
1349 }
1350
1351 private static Method parseMethodSpec(String methodSpec, Map primClasses)
1352 throws
1353 ClassNotFoundException,
1354 NoSuchMethodException
1355 {
1356 int brace = methodSpec.indexOf('(');
1357 int dot = methodSpec.lastIndexOf('.', brace);
1358 Class clazz = ClassUtil.forName(methodSpec.substring(0, dot));
1359 String methodName = methodSpec.substring(dot + 1, brace);
1360 String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 1);
1361 StringTokenizer tok = new StringTokenizer(argSpec, ",");
1362 int argcount = tok.countTokens();
1363 Class[] argTypes = new Class[argcount];
1364 for (int i = 0; i < argcount; i++)
1365 {
1366 String argClassName = tok.nextToken();
1367 argTypes[i] = (Class)primClasses.get(argClassName);
1368 if(argTypes[i] == null)
1369 {
1370 argTypes[i] = ClassUtil.forName(argClassName);
1371 }
1372 }
1373 return clazz.getMethod(methodName, argTypes);
1374 }
1375
1376 private static Map createPrimitiveClassesMap()
1377 {
1378 Map map = new HashMap();
1379 map.put("boolean", Boolean.TYPE);
1380 map.put("byte", Byte.TYPE);
1381 map.put("char", Character.TYPE);
1382 map.put("short", Short.TYPE);
1383 map.put("int", Integer.TYPE);
1384 map.put("long", Long.TYPE);
1385 map.put("float", Float.TYPE);
1386 map.put("double", Double.TYPE);
1387 return map;
1388 }
1389
1390
1391 /**
1392 * Converts any {@link BigDecimal}s in the passed array to the type of
1393 * the corresponding formal argument of the method.
1394 */
1395 public static void coerceBigDecimals(AccessibleObject callable, Object[] args)
1396 {
1397 Class[] formalTypes = null;
1398 for(int i = 0; i < args.length; ++i) {
1399 Object arg = args[i];
1400 if(arg instanceof BigDecimal) {
1401 if(formalTypes == null) {
1402 if(callable instanceof Method) {
1403 formalTypes = ((Method)callable).getParameterTypes();
1404 }
1405 else if(callable instanceof Constructor) {
1406 formalTypes = ((Constructor)callable).getParameterTypes();
1407 }
1408 else {
1409 throw new IllegalArgumentException("Expected method or "
1410 + " constructor; callable is " +
1411 callable.getClass().getName());
1412 }
1413 }
1414 args[i] = coerceBigDecimal((BigDecimal)arg, formalTypes[i]);
1415 }
1416 }
1417 }
1418
1419 /**
1420 * Converts any {@link BigDecimal}s in the passed array to the type of
1421 * the corresponding formal argument of the method.
1422 */
1423 public static void coerceBigDecimals(Class[] formalTypes, Object[] args)
1424 {
1425 int typeLen = formalTypes.length;
1426 int argsLen = args.length;
1427 int min = Math.min(typeLen, argsLen);
1428 for(int i = 0; i < min; ++i) {
1429 Object arg = args[i];
1430 if(arg instanceof BigDecimal) {
1431 args[i] = coerceBigDecimal((BigDecimal)arg, formalTypes[i]);
1432 }
1433 }
1434 if(argsLen > typeLen) {
1435 Class varArgType = formalTypes[typeLen - 1];
1436 for(int i = typeLen; i < argsLen; ++i) {
1437 Object arg = args[i];
1438 if(arg instanceof BigDecimal) {
1439 args[i] = coerceBigDecimal((BigDecimal)arg, varArgType);
1440 }
1441 }
1442 }
1443 }
1444
1445 public static Object coerceBigDecimal(BigDecimal bd, Class formalType) {
1446 // int is expected in most situations, so we check it first
1447 if(formalType == Integer.TYPE || formalType == Integer.class) {
1448 return new Integer(bd.intValue());
1449 }
1450 else if(formalType == Double.TYPE || formalType == Double.class) {
1451 return new Double(bd.doubleValue());
1452 }
1453 else if(formalType == Long.TYPE || formalType == Long.class) {
1454 return new Long(bd.longValue());
1455 }
1456 else if(formalType == Float.TYPE || formalType == Float.class) {
1457 return new Float(bd.floatValue());
1458 }
1459 else if(formalType == Short.TYPE || formalType == Short.class) {
1460 return new Short(bd.shortValue());
1461 }
1462 else if(formalType == Byte.TYPE || formalType == Byte.class) {
1463 return new Byte(bd.byteValue());
1464 }
1465 else if(BIGINTEGER_CLASS.isAssignableFrom(formalType)) {
1466 return bd.toBigInteger();
1467 }
1468 return bd;
1469 }
1470
1471 private static ClassBasedModelFactory createEnumModels(BeansWrapper wrapper) {
1472 if(ENUMS_MODEL_CTOR != null) {
1473 try {
1474 return (ClassBasedModelFactory)ENUMS_MODEL_CTOR.newInstance(
1475 new Object[] { wrapper });
1476 } catch(Exception e) {
1477 throw new UndeclaredThrowableException(e);
1478 }
1479 } else {
1480 return null;
1481 }
1482 }
1483
1484 private static Constructor enumsModelCtor() {
1485 try {
1486 // Check if Enums are available on this platform
1487 Class.forName("java.lang.Enum");
1488 // If they are, return the appropriate constructor for enum models
1489 return Class.forName(
1490 "freemarker.ext.beans.EnumModels").getDeclaredConstructor(
1491 new Class[] { BeansWrapper.class });
1492 }
1493 catch(Exception e) {
1494 // Otherwise, return null
1495 return null;
1496 }
1497 }
1498 }