KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > freemarker > ext > beans > BeansWrapper


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 JavaDoc;
56 import java.beans.IndexedPropertyDescriptor JavaDoc;
57 import java.beans.IntrospectionException JavaDoc;
58 import java.beans.Introspector JavaDoc;
59 import java.beans.MethodDescriptor JavaDoc;
60 import java.beans.PropertyDescriptor JavaDoc;
61 import java.io.InputStream JavaDoc;
62 import java.lang.reflect.AccessibleObject JavaDoc;
63 import java.lang.reflect.Constructor JavaDoc;
64 import java.lang.reflect.InvocationTargetException JavaDoc;
65 import java.lang.reflect.Method JavaDoc;
66 import java.lang.reflect.Modifier JavaDoc;
67 import java.math.BigDecimal JavaDoc;
68 import java.util.Arrays JavaDoc;
69 import java.util.Collection JavaDoc;
70 import java.util.Collections JavaDoc;
71 import java.util.Date JavaDoc;
72 import java.util.Enumeration JavaDoc;
73 import java.util.HashMap JavaDoc;
74 import java.util.HashSet JavaDoc;
75 import java.util.Iterator JavaDoc;
76 import java.util.List JavaDoc;
77 import java.util.Map JavaDoc;
78 import java.util.Properties JavaDoc;
79 import java.util.ResourceBundle JavaDoc;
80 import java.util.Set JavaDoc;
81 import java.util.StringTokenizer JavaDoc;
82
83 import freemarker.ext.util.ModelCache;
84 import freemarker.ext.util.ModelFactory;
85 import freemarker.ext.util.WrapperTemplateModel;
86 import freemarker.log.Logger;
87 import freemarker.template.AdapterTemplateModel;
88 import freemarker.template.ObjectWrapper;
89 import freemarker.template.TemplateBooleanModel;
90 import freemarker.template.TemplateCollectionModel;
91 import freemarker.template.TemplateDateModel;
92 import freemarker.template.TemplateHashModel;
93 import freemarker.template.TemplateModel;
94 import freemarker.template.TemplateModelAdapter;
95 import freemarker.template.TemplateModelException;
96 import freemarker.template.TemplateNumberModel;
97 import freemarker.template.TemplateScalarModel;
98 import freemarker.template.TemplateSequenceModel;
99 import freemarker.template.utility.ClassUtil;
100 import freemarker.template.utility.Collections12;
101 import freemarker.template.utility.SecurityUtilities;
102
103 /**
104  * Utility class that provides generic services to reflection classes.
105  * It handles all polymorphism issues in the {@link #wrap(Object)} and {@link #unwrap(TemplateModel)} methods.
106  * @author Attila Szegedi
107  * @version $Id: BeansWrapper.java,v 1.91 2005/06/13 21:34:47 szegedia Exp $
108  */

109 public class BeansWrapper implements ObjectWrapper
110 {
111     private static final Class JavaDoc BIGINTEGER_CLASS = java.math.BigInteger JavaDoc.class;
112     private static final Class JavaDoc BOOLEAN_CLASS = Boolean JavaDoc.class;
113     private static final Class JavaDoc CHARACTER_CLASS = Character JavaDoc.class;
114     private static final Class JavaDoc COLLECTION_CLASS = Collection JavaDoc.class;
115     private static final Class JavaDoc DATE_CLASS = Date JavaDoc.class;
116     private static final Class JavaDoc LIST_CLASS = List JavaDoc.class;
117     private static final Class JavaDoc MAP_CLASS = Map JavaDoc.class;
118     private static final Class JavaDoc NUMBER_CLASS = Number JavaDoc.class;
119     private static final Class JavaDoc OBJECT_CLASS = Object JavaDoc.class;
120     private static final Class JavaDoc SET_CLASS = Set JavaDoc.class;
121     private static final Class JavaDoc STRING_CLASS = String JavaDoc.class;
122     private static final Class JavaDoc TEMPLATE_MODEL_CLASS = TemplateModel.class;
123     
124     // When this property is true, some things are stricter. This is mostly to
125
// catch anomalous things in development that can otherwise be valid situations
126
// for our users.
127
private static final boolean DEVELOPMENT = "true".equals(SecurityUtilities.getSystemProperty("freemarker.development"));
128     
129     private static final Logger logger = Logger.getLogger("freemarker.beans");
130     
131     private static final Set JavaDoc UNSAFE_METHODS = createUnsafeMethodsSet();
132     
133     static final Object JavaDoc GENERIC_GET_KEY = new Object JavaDoc();
134     private static final Object JavaDoc CONSTRUCTORS = new Object JavaDoc();
135     private static final Object JavaDoc ARGTYPES = new Object JavaDoc();
136     
137     /**
138      * The default instance of BeansWrapper
139      */

140     private static final BeansWrapper INSTANCE = new BeansWrapper();
141
142     // Cache of hash maps that contain already discovered properties and methods
143
// for a specified class. Each key is a Class, each value is a hash map. In
144
// that hash map, each key is a property/method name, each value is a
145
// MethodDescriptor or a PropertyDescriptor assigned to that property/method.
146
private final Map JavaDoc classCache = new HashMap JavaDoc();
147     private Set JavaDoc cachedClassNames = new HashSet JavaDoc();
148
149     private final StaticModels staticModels = new StaticModels(this);
150
151     private final ModelCache modelCache = new ModelCache(this);
152     
153     private final BooleanModel FALSE = new BooleanModel(Boolean.FALSE, this);
154     private final BooleanModel TRUE = new BooleanModel(Boolean.TRUE, this);
155
156     /**
157      * At this level of exposure, all methods and properties of the
158      * wrapped objects are exposed to the template.
159      */

160     public static final int EXPOSE_ALL = 0;
161     
162     /**
163      * At this level of exposure, all methods and properties of the wrapped
164      * objects are exposed to the template except methods that are deemed
165      * not safe. The not safe methods are java.lang.Object methods wait() and
166      * notify(), java.lang.Class methods getClassLoader() and newInstance(),
167      * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and
168      * newInstance() methods, all java.lang.reflect.Field set methods, all
169      * java.lang.Thread and java.lang.ThreadGroup methods that can change its
170      * state, as well as the usual suspects in java.lang.System and
171      * java.lang.Runtime.
172      */

173     public static final int EXPOSE_SAFE = 1;
174     
175     /**
176      * At this level of exposure, only property getters are exposed.
177      * Additionally, property getters that map to unsafe methods are not
178      * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
179      */

180     public static final int EXPOSE_PROPERTIES_ONLY = 2;
181
182     /**
183      * At this level of exposure, no bean properties and methods are exposed.
184      * Only map items, resource bundle items, and objects retrieved through
185      * the generic get method (on objects of classes that have a generic get
186      * method) can be retrieved through the hash interface. You might want to
187      * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to
188      * speed up map item retrieval.
189      */

190     public static final int EXPOSE_NOTHING = 3;
191
192     private int exposureLevel = EXPOSE_SAFE;
193     private TemplateModel nullModel = null;
194     private boolean methodsShadowItems = true;
195     private int defaultDateType = TemplateDateModel.UNKNOWN;
196
197     private ObjectWrapper outerIdentity = this;
198     private boolean simpleMapWrapper;
199     
200     /**
201      * Creates a new instance of BeansWrapper. The newly created instance
202      * will use the null reference as its null object, it will use
203      * {@link #EXPOSE_SAFE} method exposure level, and will not cache
204      * model instances.
205      */

206     public BeansWrapper()
207     {
208     }
209
210     /**
211      * When wrapping an object, the BeansWrapper commonly needs to wrap
212      * "sub-objects", for example each element in a wrapped collection.
213      * Normally it wraps these objects using itself. However, this makes
214      * it difficult to delegate to a BeansWrapper as part of a custom
215      * aggregate ObjectWrapper. This method lets you set the ObjectWrapper
216      * which will be used to wrap the sub-objects.
217      * @param outerIdentity the aggregate ObjectWrapper
218      */

219     public void setOuterIdentity(ObjectWrapper outerIdentity)
220     {
221         this.outerIdentity = outerIdentity;
222     }
223
224     /**
225      * By default returns <tt>this</tt>.
226      * @see #setOuterIdentity(ObjectWrapper)
227      */

228     public ObjectWrapper getOuterIdentity()
229     {
230         return outerIdentity;
231     }
232
233     /**
234      * By default the BeansWrapper wraps classes implementing
235      * java.util.Map using {@link MapModel}. Setting this flag will
236      * cause it to use a {@link SimpleMapModel} instead. The biggest
237      * difference is that when using a {@link SimpleMapModel}, the
238      * map will be visible as <code>TemplateHashModelEx</code>,
239      * and the subvariables will be the content of the map,
240      * without the other methods and properties of the map object.
241      * @param simpleMapWrapper enable simple map wrapping
242      */

243     public void setSimpleMapWrapper(boolean simpleMapWrapper)
244     {
245         this.simpleMapWrapper = simpleMapWrapper;
246     }
247
248     public boolean isSimpleMapWrapper()
249     {
250         return simpleMapWrapper;
251     }
252
253     /**
254      * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
255      * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
256      * constants.
257      */

258     public void setExposureLevel(int exposureLevel)
259     {
260         if(exposureLevel < EXPOSE_ALL || exposureLevel > EXPOSE_NOTHING)
261         {
262             throw new IllegalArgumentException JavaDoc("Illegal exposure level " + exposureLevel);
263         }
264         this.exposureLevel = exposureLevel;
265     }
266     
267     int getExposureLevel()
268     {
269         return exposureLevel;
270     }
271     
272     /**
273      * Sets whether methods shadow items in beans. When true (this is the
274      * default value), <code>${object.name}</code> will first try to locate
275      * a bean method or property with the specified name on the object, and
276      * only if it doesn't find it will it try to call
277      * <code>object.get(name)</code>, the so-called "generic get method" that
278      * is usually used to access items of a container (i.e. elements of a map).
279      * When set to false, the lookup order is reversed and generic get method
280      * is called first, and only if it returns null is method lookup attempted.
281      */

282     public synchronized void setMethodsShadowItems(boolean methodsShadowItems)
283     {
284         this.methodsShadowItems = methodsShadowItems;
285     }
286     
287     boolean isMethodsShadowItems()
288     {
289         return methodsShadowItems;
290     }
291     
292     /**
293      * Sets the default date type to use for date models that result from
294      * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
295      * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is
296      * {@link TemplateDateModel#UNKNOWN}.
297      * @param defaultDateType the new default date type.
298      */

299     public synchronized void setDefaultDateType(int defaultDateType) {
300         this.defaultDateType = defaultDateType;
301     }
302     
303     protected int getDefaultDateType() {
304         return defaultDateType;
305     }
306     
307     /**
308      * Sets whether this wrapper caches model instances. Default is false.
309      * When set to true, calling {@link #wrap(Object)} multiple times for
310      * the same object will likely return the same model (although there is
311      * no guarantee as the cache items can be cleared anytime).
312      */

313     public void setUseCache(boolean useCache)
314     {
315         modelCache.setUseCache(useCache);
316     }
317     
318     /**
319      * Sets the null model. This model is returned from the
320      * {@link #wrap(Object)} method whenever the underlying object
321      * reference is null. It defaults to null reference, which is dealt
322      * with quite strictly on engine level, however you can substitute an
323      * arbitrary (perhaps more lenient) model, such as
324      * {@link freemarker.template.TemplateScalarModel#EMPTY_STRING}.
325      */

326     public void setNullModel(TemplateModel nullModel)
327     {
328         this.nullModel = nullModel;
329     }
330     
331     /**
332      * Returns the default instance of the wrapper. This instance is used
333      * when you construct various bean models without explicitly specifying
334      * a wrapper. It is also returned by
335      * {@link freemarker.template.ObjectWrapper#BEANS_WRAPPER}
336      * and this is the sole instance that is used by the JSP adapter.
337      * You can modify the properties of the default instance (caching,
338      * exposure level, null model) to affect its operation. By default, the
339      * default instance is not caching, uses the <code>EXPOSE_SAFE</code>
340      * exposure level, and uses null reference as the null model.
341      */

342     public static final BeansWrapper getDefaultInstance()
343     {
344         return INSTANCE;
345     }
346
347     /**
348      * Wraps the object with a template model that is most specific for the object's
349      * class. Specifically:
350      * <ul>
351      * <li>if the object is null, returns the {@link #setNullModel(TemplateModel) null model},</li>
352      * <li>if the object is a Number returns a {@link NumberModel} for it,</li>
353      * <li>if the object is a Date returns a {@link DateModel} for it,</li>
354      * <li>if the object is a Boolean returns
355      * {@link freemarker.template.TemplateBooleanModel#TRUE} or
356      * {@link freemarker.template.TemplateBooleanModel#FALSE}</li>
357      * <li>if the object is already a TemplateModel, returns it unchanged,</li>
358      * <li>if the object is an array, returns a {@link ArrayModel} for it
359      * <li>if the object is a Map, returns a {@link MapModel} for it
360      * <li>if the object is a Collection, returns a {@link CollectionModel} for it
361      * <li>if the object is an Iterator, returns a {@link IteratorModel} for it
362      * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it
363      * <li>if the object is a String, returns a {@link StringModel} for it
364      * <li>otherwise, returns a generic {@link BeanModel} for it.
365      * </ul>
366      */

367     public TemplateModel wrap(Object JavaDoc object) throws TemplateModelException
368     {
369         if(object == null)
370             return nullModel;
371         if(object instanceof TemplateModel)
372             return (TemplateModel)object;
373         if(object instanceof TemplateModelAdapter)
374             return ((TemplateModelAdapter)object).getTemplateModel();
375         if(object instanceof Map JavaDoc)
376             return modelCache.getInstance(object, simpleMapWrapper ? SimpleMapModel.FACTORY : MapModel.FACTORY);
377         if(object instanceof Collection JavaDoc)
378             return modelCache.getInstance(object, CollectionModel.FACTORY);
379         if(object.getClass().isArray())
380             return modelCache.getInstance(object, ArrayModel.FACTORY);
381         if(object instanceof Number JavaDoc)
382             return modelCache.getInstance(object, NumberModel.FACTORY);
383         if(object instanceof Date JavaDoc)
384             return modelCache.getInstance(object, DateModel.FACTORY);
385         if(object instanceof Boolean JavaDoc)
386             return ((Boolean JavaDoc)object).booleanValue() ? TRUE : FALSE;
387         if(object instanceof ResourceBundle JavaDoc)
388             return modelCache.getInstance(object, ResourceBundleModel.FACTORY);
389         if(object instanceof Iterator JavaDoc)
390             return new IteratorModel((Iterator JavaDoc)object, this);
391         if(object instanceof Enumeration JavaDoc)
392             return new EnumerationModel((Enumeration JavaDoc)object, this);
393         return modelCache.getInstance(object, StringModel.FACTORY);
394     }
395
396     protected TemplateModel getInstance(Object JavaDoc object, ModelFactory factory)
397     {
398         return modelCache.getInstance(object, factory);
399     }
400     
401     protected TemplateModel create(Object JavaDoc object, Object JavaDoc factory)
402     {
403         return ((ModelFactory)factory).create(object, this);
404     }
405
406     /**
407      * Attempts to unwrap a model into underlying object. Generally, this
408      * method is the inverse of the {@link #wrap(Object)} method. In addition
409      * it will unwrap arbitrary {@link TemplateNumberModel} instances into
410      * a number, arbitrary {@link TemplateDateModel} instances into a date,
411      * {@link TemplateScalarModel} instances into a String, and
412      * {@link TemplateBooleanModel} instances into a Boolean.
413      * All other objects are returned unchanged.
414      */

415     public Object JavaDoc unwrap(TemplateModel model) throws TemplateModelException
416     {
417         return unwrap(model, OBJECT_CLASS);
418     }
419     
420     public Object JavaDoc unwrap(TemplateModel model, Class JavaDoc hint)
421     throws TemplateModelException
422     {
423         if(model == nullModel) {
424             return null;
425         }
426         
427         // This is to be able to pass TemplateModel objects to methods that
428
// explicitly declare them in their argument types
429
if(TEMPLATE_MODEL_CLASS.isAssignableFrom(hint)) {
430             return model;
431         }
432         
433         // This is for transparent interop with other wrappers (and ourselves)
434
// Passing the hint allows i.e. a Jython-aware method that declares a
435
// PyObject as its argument to receive a PyObject from a JythonModel
436
// passed as an argument to TemplateMethodModelEx etc.
437
if(model instanceof AdapterTemplateModel) {
438             return ((AdapterTemplateModel)model).getAdaptedObject(hint);
439         }
440         if(model instanceof WrapperTemplateModel) {
441             return ((WrapperTemplateModel)model).getWrappedObject();
442         }
443         
444         // Translation of generic template models to POJOs. First give priority
445
// to various model interfaces based on the hint class. This helps us
446
// select the appropriate interface in multi-interface models when we
447
// know what is expected as the return type.
448

449         if(STRING_CLASS == hint) {
450             if(model instanceof TemplateScalarModel) {
451                 return ((TemplateScalarModel)model).getAsString();
452             }
453         }
454         boolean isBoolean = Boolean.TYPE == hint;
455         if(isBoolean || BOOLEAN_CLASS == hint) {
456             if(model instanceof TemplateBooleanModel) {
457                 return ((TemplateBooleanModel)model).getAsBoolean() ? Boolean.TRUE : Boolean.FALSE;
458             }
459         }
460         if(MAP_CLASS == hint) {
461             if(model instanceof TemplateHashModel) {
462                 return new HashAdapter((TemplateHashModel)model, this);
463             }
464         }
465         if(LIST_CLASS == hint) {
466             if(model instanceof TemplateSequenceModel) {
467                 return new SequenceAdapter((TemplateSequenceModel)model, this);
468             }
469         }
470         if(SET_CLASS == hint || COLLECTION_CLASS == hint) {
471             if(model instanceof TemplateCollectionModel) {
472                 return new CollectionAdapter((TemplateCollectionModel)model, this);
473             }
474         }
475         
476         // Allow one-char strings to be coerced to characters
477
boolean isChar = hint == Character.TYPE;
478         if(isChar || hint == CHARACTER_CLASS) {
479             if(model instanceof TemplateScalarModel) {
480                 String JavaDoc s = ((TemplateScalarModel)model).getAsString();
481                 if(s.length() == 1) {
482                     return new Character JavaDoc(s.charAt(0));
483                 }
484             }
485         }
486
487         // Primitive numeric types & Number.class
488
if((hint.isPrimitive() && !isChar && !isBoolean)
489                 || NUMBER_CLASS.isAssignableFrom(hint)) {
490             if(model instanceof TemplateNumberModel) {
491                 return ((TemplateNumberModel)model).getAsNumber();
492             }
493         }
494         if(DATE_CLASS.isAssignableFrom(hint)) {
495             if(model instanceof TemplateDateModel) {
496                 return ((TemplateDateModel)model).getAsDate();
497             }
498         }
499         
500         // Translation of generic template models to POJOs. Since hint was of
501
// no help, now use an admittedly arbitrary order of interfaces.
502
if(model instanceof TemplateNumberModel)
503             return ((TemplateNumberModel)model).getAsNumber();
504         if(model instanceof TemplateDateModel)
505             return ((TemplateDateModel)model).getAsDate();
506         if(model instanceof TemplateScalarModel)
507             return ((TemplateScalarModel)model).getAsString();
508         if(model instanceof TemplateBooleanModel)
509             return ((TemplateBooleanModel)model).getAsBoolean() ? Boolean.TRUE : Boolean.FALSE;
510         if(model instanceof TemplateHashModel)
511             return new HashAdapter((TemplateHashModel)model, this);
512         if(model instanceof TemplateSequenceModel)
513             return new SequenceAdapter((TemplateSequenceModel)model, this);
514         if(model instanceof TemplateCollectionModel)
515             return new CollectionAdapter((TemplateCollectionModel)model, this);
516         
517         // If everything else fails, return the model as-is
518
// (TemplateTransformModel and TemplateMethodModel[Ex] will fall into
519
// this category.
520
return model;
521     }
522     
523     /**
524      * Auxiliary method that unwraps arguments for a method or constructor call.
525      * @param arguments the argument list of template models
526      * @param argTypes the preferred types of the arguments
527      * @return Object[] the unwrapped arguments. null if the passed list was
528      * null.
529      * @throws TemplateModelException if unwrapping any argument throws one
530      */

531     Object JavaDoc[] unwrapArguments(List JavaDoc arguments, Class JavaDoc[] argTypes) throws
532     TemplateModelException
533     {
534         Object JavaDoc[] args = null;
535         if(arguments != null) {
536             int size = arguments.size();
537             args = new Object JavaDoc[size];
538             Iterator JavaDoc it = arguments.iterator();
539             for(int i = 0; it.hasNext(); ++i) {
540                 args[i] = unwrap((TemplateModel)it.next(), argTypes[i]);
541             }
542         }
543         return args;
544     }
545     
546     Object JavaDoc[] unwrapArguments(List JavaDoc arguments) throws TemplateModelException
547     {
548         Object JavaDoc[] args = null;
549         if(arguments != null) {
550             int size = arguments.size();
551             args = new Object JavaDoc[size];
552             Iterator JavaDoc it = arguments.iterator();
553             int i = 0;
554             while(it.hasNext()) {
555                 args[i++] = unwrap((TemplateModel)it.next());
556             }
557         }
558         return args;
559     }
560
561     /**
562      * Invokes the specified method, wrapping the return value. The specialty
563      * of this method is that if the return value is null, and the return type
564      * of the invoked method is void, {@link TemplateModel#NOTHING} is returned.
565      * @param object the object to invoke the method on
566      * @param method the method to invoke
567      * @param args the arguments to the method
568      * @return the wrapped return value of the method.
569      * @throws InvocationTargetException if the invoked method threw an exception
570      * @throws IllegalAccessException if the method can't be invoked due to an
571      * access restriction.
572      * @throws TemplateModelException if the return value couldn't be wrapped
573      * (this can happen if the wrapper has an outer identity or is subclassed,
574      * and the outer identity or the subclass throws an exception. Plain
575      * BeansWrapper never throws TemplateModelException).
576      */

577     TemplateModel invokeMethod(Object JavaDoc object, Method JavaDoc method, Object JavaDoc[] args)
578     throws
579         InvocationTargetException JavaDoc,
580         IllegalAccessException JavaDoc,
581         TemplateModelException
582     {
583         Object JavaDoc retval = method.invoke(object, args);
584         return
585             method.getReturnType() == Void.TYPE
586             ? TemplateModel.NOTHING
587             : getOuterIdentity().wrap(retval);
588     }
589
590    /**
591      * Returns a hash model that represents the so-called class static models.
592      * Every class static model is itself a hash through which you can call
593      * static methods on the specified class. To obtain a static model for a
594      * class, get the element of this hash with the fully qualified class name.
595      * For example, if you place this hash model inside the root data model
596      * under name "statics", you can use i.e. <code>statics["java.lang.
597      * System"]. currentTimeMillis()</code> to call the {@link
598      * java.lang.System#currentTimeMillis()} method.
599      * @return a hash model whose keys are fully qualified class names, and
600      * that returns hash models whose elements are the static models of the
601      * classes.
602      */

603     public TemplateHashModel getStaticModels()
604     {
605         return staticModels;
606     }
607     
608     public Object JavaDoc newInstance(Class JavaDoc clazz, List JavaDoc arguments)
609     throws
610         TemplateModelException
611     {
612         try
613         {
614             introspectClass(clazz);
615             Map JavaDoc classInfo = (Map JavaDoc)classCache.get(clazz);
616             Object JavaDoc ctors = classInfo.get(CONSTRUCTORS);
617             if(ctors == null)
618             {
619                 throw new TemplateModelException("Class " + clazz.getName() +
620                         " has no public constructors.");
621             }
622             Constructor JavaDoc ctor = null;
623             Object JavaDoc[] objargs;
624             if(ctors instanceof Constructor JavaDoc)
625             {
626                 ctor = (Constructor JavaDoc)ctors;
627                 objargs = unwrapArguments(arguments, getArgTypes(classInfo, ctor));
628             }
629             else if(ctors instanceof MethodMap)
630             {
631                 MethodMap methodMap = (MethodMap)ctors;
632                 objargs = unwrapArguments(arguments, methodMap.getUnwrapTypes(arguments));
633                 ctor = (Constructor JavaDoc)methodMap.getMostSpecific(objargs);
634             }
635             else
636             {
637                 // Cannot happen
638
throw new Error JavaDoc();
639             }
640             if(objargs != null) {
641                 coerceBigDecimals(ctor, objargs);
642             }
643             return ctor.newInstance(objargs);
644         }
645         catch (TemplateModelException e)
646         {
647             throw e;
648         }
649         catch (Exception JavaDoc e)
650         {
651             throw new TemplateModelException(
652                     "Could not create instance of class " + clazz.getName(), e);
653         }
654     }
655     
656     void introspectClass(Class JavaDoc clazz)
657     {
658         synchronized(classCache)
659         {
660             if(!classCache.containsKey(clazz))
661             {
662                 String JavaDoc className = clazz.getName();
663                 if(cachedClassNames.contains(className))
664                 {
665                     if(logger.isInfoEnabled())
666                     {
667                         logger.info("Detected a reloaded class [" + className +
668                                 "]. Clearing BeansWrapper caches.");
669                     }
670                     // Class reload detected, throw away caches
671
classCache.clear();
672                     cachedClassNames = new HashSet JavaDoc();
673                     synchronized(this)
674                     {
675                         modelCache.clearCache();
676                     }
677                     staticModels.clearCache();
678                 }
679                 classCache.put(clazz, populateClassMap(clazz));
680                 cachedClassNames.add(className);
681             }
682         }
683     }
684
685     Map JavaDoc getClassKeyMap(Class JavaDoc clazz)
686     {
687         synchronized(classCache)
688         {
689             return (Map JavaDoc)classCache.get(clazz);
690         }
691     }
692
693     /**
694      * Returns the number of introspected methods/properties that should
695      * be available via the TemplateHashModel interface. Affected by the
696      * {@link #setMethodsShadowItems(boolean)} and {@link
697      * #setExposureLevel(int)} settings.
698      */

699     int keyCount(Class JavaDoc clazz)
700     {
701         Map JavaDoc map = getClassKeyMap(clazz);
702         int count = map.size();
703         if (map.containsKey(CONSTRUCTORS))
704             count--;
705         if (map.containsKey(GENERIC_GET_KEY))
706             count--;
707         if (map.containsKey(ARGTYPES))
708             count--;
709         return count;
710     }
711
712     /**
713      * Returns the Set of names of introspected methods/properties that
714      * should be available via the TemplateHashModel interface. Affected
715      * by the {@link #setMethodsShadowItems(boolean)} and {@link
716      * #setExposureLevel(int)} settings.
717      */

718     Set JavaDoc keySet(Class JavaDoc clazz)
719     {
720         Set JavaDoc set = new HashSet JavaDoc(getClassKeyMap(clazz).keySet());
721         set.remove(CONSTRUCTORS);
722         set.remove(GENERIC_GET_KEY);
723         set.remove(ARGTYPES);
724         return set;
725     }
726     
727     /**
728      * Populates a map with property and method descriptors for a specified
729      * class. If any property or method descriptors specifies a read method
730      * that is not accessible, replaces it with appropriate accessible method
731      * from a superclass or interface.
732      */

733     private Map JavaDoc populateClassMap(Class JavaDoc clazz)
734     {
735         // Populate first from bean info
736
Map JavaDoc map = populateClassMapWithBeanInfo(clazz);
737         // Next add constructors
738
try
739         {
740             Constructor JavaDoc[] ctors = clazz.getConstructors();
741             if(ctors.length == 1)
742             {
743                 Constructor JavaDoc ctor = ctors[0];
744                 map.put(CONSTRUCTORS, ctor);
745                 getArgTypes(map).put(ctor, ctor.getParameterTypes());
746             }
747             else if(ctors.length > 1)
748             {
749                 MethodMap ctorMap = new MethodMap("<init>");
750                 for (int i = 0; i < ctors.length; i++)
7