KickJava   Java API By Example, From Geeks To Geeks.

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


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.IndexedPropertyDescriptor JavaDoc;
56 import java.beans.PropertyDescriptor JavaDoc;
57 import java.lang.reflect.InvocationTargetException JavaDoc;
58 import java.lang.reflect.Method JavaDoc;
59 import java.util.ArrayList JavaDoc;
60 import java.util.Collection JavaDoc;
61 import java.util.HashMap JavaDoc;
62 import java.util.List JavaDoc;
63 import java.util.Map JavaDoc;
64 import java.util.Set JavaDoc;
65
66 import freemarker.ext.util.ModelFactory;
67 import freemarker.ext.util.WrapperTemplateModel;
68 import freemarker.log.Logger;
69 import freemarker.core.CollectionAndSequence;
70 import freemarker.template.ObjectWrapper;
71 import freemarker.template.SimpleSequence;
72 import freemarker.template.TemplateCollectionModel;
73 import freemarker.template.TemplateHashModelEx;
74 import freemarker.template.TemplateModel;
75 import freemarker.template.TemplateModelException;
76 import freemarker.template.TemplateModelIterator;
77 import freemarker.template.TemplateScalarModel;
78
79 /**
80  * A class that will wrap an arbitrary object into {@link freemarker.template.TemplateHashModel}
81  * interface allowing calls to arbitrary property getters and invocation of
82  * accessible methods on the object from a template using the
83  * <tt>object.foo</tt> to access properties and <tt>object.bar(arg1, arg2)</tt> to
84  * invoke methods on it. You can also use the <tt>object.foo[index]</tt> syntax to
85  * access indexed properties. It uses Beans {@link java.beans.Introspector}
86  * to dynamically discover the properties and methods.
87  * @author Attila Szegedi
88  * @version $Id: BeanModel.java,v 1.46 2004/01/06 17:06:42 szegedia Exp $
89  */

90
91 public class BeanModel
92     implements
93     TemplateHashModelEx, WrapperTemplateModel
94 {
95     private static final Logger logger = Logger.getLogger("freemarker.beans");
96     protected final Object JavaDoc object;
97     protected final BeansWrapper wrapper;
98     
99     static final ModelFactory FACTORY =
100         new ModelFactory()
101         {
102             public TemplateModel create(Object JavaDoc object, ObjectWrapper wrapper)
103             {
104                 return new BeanModel(object, (BeansWrapper)wrapper);
105             }
106         };
107
108     // Cached template models that implement member properties and methods for this
109
// instance. Keys are FeatureDescriptor instances (from classCache values),
110
// values are either ReflectionMethodModels/ReflectionScalarModels
111
private final HashMap JavaDoc memberMap = new HashMap JavaDoc();
112
113     /**
114      * Creates a new model that wraps the specified object. Note that there are
115      * specialized subclasses of this class for wrapping arrays, collections,
116      * enumeration, iterators, and maps. Note also that the superclass can be
117      * used to wrap String objects if only scalar functionality is needed. You
118      * can also choose to delegate the choice over which model class is used for
119      * wrapping to {@link BeansWrapper#wrap(Object)}.
120      * @param object the object to wrap into a model.
121      * @param wrapper the {@link BeansWrapper} associated with this model.
122      * Every model has to have an associated {@link BeansWrapper} instance. The
123      * model gains many attributes from its wrapper, including the caching
124      * behavior, method exposure level, method-over-item shadowing policy etc.
125      */

126     public BeanModel(Object JavaDoc object, BeansWrapper wrapper)
127     {
128         this.object = object;
129         this.wrapper = wrapper;
130         if (object == null) {
131             return;
132         }
133         wrapper.introspectClass(object.getClass());
134     }
135
136     /**
137      * Uses Beans introspection to locate a property or method with name
138      * matching the key name. If a method or property is found, it is wrapped
139      * into {@link freemarker.template.TemplateMethodModelEx} (for a method or
140      * indexed property), or evaluated on-the-fly and the return value wrapped
141      * into appropriate model (for a simple property) Models for various
142      * properties and methods are cached on a per-class basis, so the costly
143      * introspection is performed only once per property or method of a class.
144      * (Side-note: this also implies that any class whose method has been called
145      * will be strongly referred to by the framework and will not become
146      * unloadable until this class has been unloaded first. Normally this is not
147      * an issue, but can be in a rare scenario where you create many classes on-
148      * the-fly. Also, as the cache grows with new classes and methods introduced
149      * to the framework, it may appear as if it were leaking memory. The
150      * framework does, however detect class reloads (if you happen to be in an
151      * environment that does this kind of things--servlet containers do it when
152      * they reload a web application) and flushes the cache. If no method or
153      * property matching the key is found, the framework will try to invoke
154      * methods with signature
155      * <tt>non-void-return-type get(java.lang.String)</tt>,
156      * then <tt>non-void-return-type get(java.lang.Object)</tt>, or
157      * alternatively (if the wrapped object is a resource bundle)
158      * <tt>Object getObject(java.lang.String)</tt>.
159      * @throws TemplateModelException if there was no property nor method nor
160      * a generic <tt>get</tt> method to invoke.
161      */

162     public TemplateModel get(String JavaDoc key)
163         throws
164         TemplateModelException
165     {
166         Class JavaDoc clazz = object.getClass();
167         Map JavaDoc keyMap = wrapper.getClassKeyMap(clazz);
168         
169         try
170         {
171             if(wrapper.isMethodsShadowItems())
172             {
173                 Object JavaDoc fd = keyMap.get(key);
174                 if(fd != null)
175                 {
176                     return invokeThroughDescriptor(fd);
177                 }
178                 TemplateModel retval = invokeGenericGet(keyMap, clazz, key);
179                 if(retval == null && logger.isDebugEnabled())
180                 {
181                     logNoSuchKey(key, keyMap);
182                 }
183                 return retval;
184             }
185             else
186             {
187                 TemplateModel model = invokeGenericGet(keyMap, clazz, key);
188                 if(model != null)
189                 {
190                     return model;
191                 }
192                 Object JavaDoc fd = keyMap.get(key);
193                 if(fd == null)
194                 {
195                     if(logger.isDebugEnabled())
196                     {
197                         logNoSuchKey(key, keyMap);
198                     }
199                     return null;
200                 }
201                 return invokeThroughDescriptor(fd);
202             }
203         }
204         catch(TemplateModelException e)
205         {
206             throw e;
207         }
208         catch(Exception JavaDoc e)
209         {
210             throw new TemplateModelException("get(" + key + ") failed on " +
211                 "instance of " + object.getClass().getName(), e);
212         }
213     }
214
215     private void logNoSuchKey(String JavaDoc key, Map JavaDoc keyMap)
216     {
217         logger.debug("Key '" + key + "' was not found on instance of " +
218             object.getClass().getName() + ". Introspection information for " +
219             "the class is: " + keyMap);
220     }
221     
222     private TemplateModel invokeThroughDescriptor(Object JavaDoc desc)
223         throws
224         IllegalAccessException JavaDoc,
225         InvocationTargetException JavaDoc,
226         TemplateModelException
227     {
228         // See if this particular instance has a cached implementation
229
// for the requested feature descriptor
230
TemplateModel member = null;
231         synchronized(memberMap)
232         {
233             member = (TemplateModel)memberMap.get(desc);
234         }
235
236         if(member != null)
237             return member;
238
239         TemplateModel retval = null;
240         if(desc instanceof IndexedPropertyDescriptor JavaDoc)
241         {
242             retval = member =
243                 new SimpleMethodModel(
244                     object,
245                     ((IndexedPropertyDescriptor JavaDoc)desc).getIndexedReadMethod(),
246                     wrapper);
247         }
248         else if(desc instanceof PropertyDescriptor JavaDoc)
249         {
250             PropertyDescriptor JavaDoc pd = (PropertyDescriptor JavaDoc)desc;
251             retval = wrapper.invokeMethod(object, pd.getReadMethod(), null);
252             // (member == null) condition remains, as we don't cache these
253
}
254         else if(desc instanceof Method JavaDoc)
255         {
256             retval = member =
257                 new SimpleMethodModel(object, (Method JavaDoc)desc, wrapper);
258         }
259         else if(desc instanceof MethodMap)
260         {
261             retval = member =
262                 new OverloadedMethodModel(object, (MethodMap)desc, wrapper);
263         }
264         
265         // If new cacheable member was created, cache it
266
if(member != null)
267         {
268             synchronized(memberMap)
269             {
270                 memberMap.put(desc, member);
271             }
272         }
273         return retval;
274     }
275
276     protected TemplateModel invokeGenericGet(Map JavaDoc keyMap, Class JavaDoc clazz, String JavaDoc key)
277     throws
278         IllegalAccessException JavaDoc,
279         InvocationTargetException JavaDoc,
280         TemplateModelException
281     {
282         Method JavaDoc genericGet = (Method JavaDoc)keyMap.get(BeansWrapper.GENERIC_GET_KEY);
283         if(genericGet == null)
284             return null;
285
286         return wrapper.invokeMethod(object, genericGet, new Object JavaDoc[] { key });
287     }
288
289     protected TemplateModel wrap(Object JavaDoc obj)
290     throws TemplateModelException
291     {
292         return wrapper.getOuterIdentity().wrap(obj);
293     }
294     
295     protected Object JavaDoc unwrap(TemplateModel model)
296     throws
297         TemplateModelException
298     {
299         return wrapper.unwrap(model);
300     }
301
302     /**
303      * Tells whether the model is empty. It is empty if either the wrapped
304      * object is null, or it is a Boolean with false value.
305      */

306     public boolean isEmpty()
307     {
308         if (object instanceof String JavaDoc) {
309             return ((String JavaDoc) object).length() == 0;
310         }
311         if (object instanceof Collection JavaDoc) {
312             return ((Collection JavaDoc) object).isEmpty();
313         }
314     if (object instanceof Map JavaDoc) {
315         return ((Map JavaDoc) object).isEmpty();
316     }
317         return object == null || Boolean.FALSE.equals(object);
318     }
319     
320     /**
321      * Returns the underlying object.
322      */

323     public Object JavaDoc getWrappedObject()
324     {
325         return object;
326     }
327
328     public int size()
329     {
330         return wrapper.keyCount(object.getClass());
331     }
332
333     public TemplateCollectionModel keys()
334     {
335         return new CollectionAndSequence(new SimpleSequence(keySet(), wrapper));
336     }
337
338     public TemplateCollectionModel values() throws TemplateModelException
339     {
340         List JavaDoc values = new ArrayList JavaDoc(size());
341         TemplateModelIterator it = keys().iterator();
342         while (it.hasNext()) {
343             String JavaDoc key = ((TemplateScalarModel)it.next()).getAsString();
344             values.add(get(key));
345         }
346         return new CollectionAndSequence(new SimpleSequence(values, wrapper));
347     }
348     
349     public String JavaDoc toString() {
350         return object.toString();
351     }
352
353     /**
354      * Helper method to support TemplateHashModelEx. Returns the Set of
355      * Strings which are available via the TemplateHashModel
356      * interface. Subclasses that override <tt>invokeGenericGet</tt> to
357      * provide additional hash keys should also override this method.
358      */

359     protected Set JavaDoc keySet()
360     {
361         return wrapper.keySet(object.getClass());
362     }
363 }
Popular Tags