Save This Page
Home » freemarker-2.3.13 » freemarker.ext.beans » [javadoc | source]
    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;
   56   import java.beans.PropertyDescriptor;
   57   import java.lang.reflect.Field;
   58   import java.lang.reflect.InvocationTargetException;
   59   import java.lang.reflect.Method;
   60   import java.util.ArrayList;
   61   import java.util.Collection;
   62   import java.util.HashMap;
   63   import java.util.List;
   64   import java.util.Map;
   65   import java.util.Set;
   66   
   67   import freemarker.core.CollectionAndSequence;
   68   import freemarker.ext.util.ModelFactory;
   69   import freemarker.ext.util.WrapperTemplateModel;
   70   import freemarker.log.Logger;
   71   import freemarker.template.AdapterTemplateModel;
   72   import freemarker.template.ObjectWrapper;
   73   import freemarker.template.SimpleScalar;
   74   import freemarker.template.SimpleSequence;
   75   import freemarker.template.TemplateCollectionModel;
   76   import freemarker.template.TemplateHashModelEx;
   77   import freemarker.template.TemplateModel;
   78   import freemarker.template.TemplateModelException;
   79   import freemarker.template.TemplateModelIterator;
   80   import freemarker.template.TemplateScalarModel;
   81   
   82   /**
   83    * A class that will wrap an arbitrary object into {@link freemarker.template.TemplateHashModel}
   84    * interface allowing calls to arbitrary property getters and invocation of
   85    * accessible methods on the object from a template using the
   86    * <tt>object.foo</tt> to access properties and <tt>object.bar(arg1, arg2)</tt> to
   87    * invoke methods on it. You can also use the <tt>object.foo[index]</tt> syntax to
   88    * access indexed properties. It uses Beans {@link java.beans.Introspector}
   89    * to dynamically discover the properties and methods. 
   90    * @author Attila Szegedi
   91    * @version $Id: BeanModel.java,v 1.49.2.4 2006/11/12 10:20:37 szegedia Exp $
   92    */
   93   
   94   public class BeanModel
   95   implements
   96       TemplateHashModelEx, AdapterTemplateModel, WrapperTemplateModel
   97   {
   98       private static final Logger logger = Logger.getLogger("freemarker.beans");
   99       protected final Object object;
  100       protected final BeansWrapper wrapper;
  101       
  102       // We use this to represent an unknown value as opposed to known value of null (JR)
  103       private static final TemplateModel UNKNOWN = new SimpleScalar("UNKNOWN");
  104       
  105       static final ModelFactory FACTORY =
  106           new ModelFactory()
  107           {
  108               public TemplateModel create(Object object, ObjectWrapper wrapper)
  109               {
  110                   return new BeanModel(object, (BeansWrapper)wrapper);
  111               }
  112           };
  113   
  114       // Cached template models that implement member properties and methods for this
  115       // instance. Keys are FeatureDescriptor instances (from classCache values),
  116       // values are either ReflectionMethodModels/ReflectionScalarModels
  117       private HashMap memberMap;
  118   
  119       /**
  120        * Creates a new model that wraps the specified object. Note that there are
  121        * specialized subclasses of this class for wrapping arrays, collections,
  122        * enumeration, iterators, and maps. Note also that the superclass can be
  123        * used to wrap String objects if only scalar functionality is needed. You
  124        * can also choose to delegate the choice over which model class is used for
  125        * wrapping to {@link BeansWrapper#wrap(Object)}.
  126        * @param object the object to wrap into a model.
  127        * @param wrapper the {@link BeansWrapper} associated with this model.
  128        * Every model has to have an associated {@link BeansWrapper} instance. The
  129        * model gains many attributes from its wrapper, including the caching 
  130        * behavior, method exposure level, method-over-item shadowing policy etc.
  131        */
  132       public BeanModel(Object object, BeansWrapper wrapper)
  133       {
  134           this.object = object;
  135           this.wrapper = wrapper;
  136           if (object == null) {
  137               return;
  138           }
  139           wrapper.introspectClass(object.getClass());
  140       }
  141   
  142       /**
  143        * Uses Beans introspection to locate a property or method with name
  144        * matching the key name. If a method or property is found, it is wrapped
  145        * into {@link freemarker.template.TemplateMethodModelEx} (for a method or
  146        * indexed property), or evaluated on-the-fly and the return value wrapped
  147        * into appropriate model (for a simple property) Models for various
  148        * properties and methods are cached on a per-class basis, so the costly
  149        * introspection is performed only once per property or method of a class.
  150        * (Side-note: this also implies that any class whose method has been called
  151        * will be strongly referred to by the framework and will not become
  152        * unloadable until this class has been unloaded first. Normally this is not
  153        * an issue, but can be in a rare scenario where you create many classes on-
  154        * the-fly. Also, as the cache grows with new classes and methods introduced
  155        * to the framework, it may appear as if it were leaking memory. The
  156        * framework does, however detect class reloads (if you happen to be in an
  157        * environment that does this kind of things--servlet containers do it when
  158        * they reload a web application) and flushes the cache. If no method or
  159        * property matching the key is found, the framework will try to invoke
  160        * methods with signature
  161        * <tt>non-void-return-type get(java.lang.String)</tt>,
  162        * then <tt>non-void-return-type get(java.lang.Object)</tt>, or 
  163        * alternatively (if the wrapped object is a resource bundle) 
  164        * <tt>Object getObject(java.lang.String)</tt>.
  165        * @throws TemplateModelException if there was no property nor method nor
  166        * a generic <tt>get</tt> method to invoke.
  167        */
  168       public TemplateModel get(String key)
  169           throws
  170           TemplateModelException
  171       {
  172           Class clazz = object.getClass();
  173           Map classInfo = wrapper.getClassKeyMap(clazz);
  174           TemplateModel retval = null;
  175           
  176           try
  177           {
  178               if(wrapper.isMethodsShadowItems())
  179               {
  180                   Object fd = classInfo.get(key);
  181                   if(fd != null)
  182                   {
  183                       retval = invokeThroughDescriptor(fd, classInfo);
  184                   } else {
  185                       retval = invokeGenericGet(classInfo, clazz, key);
  186                   }
  187               }
  188               else
  189               {
  190                   TemplateModel model = invokeGenericGet(classInfo, clazz, key);
  191                   final TemplateModel nullModel = wrapper.wrap(null);
  192                   if(model != nullModel && model != UNKNOWN) 
  193                   {
  194                       return model;
  195                   }
  196                   Object fd = classInfo.get(key);
  197                   if(fd != null) {
  198                       retval = invokeThroughDescriptor(fd, classInfo);
  199                       if (retval == UNKNOWN && model == nullModel) {
  200                       	// This is the (somewhat subtle) case where the generic get() returns null
  201                       	// and we have no bean info, so we respect the fact that
  202                       	// the generic get() returns null and return null. (JR)
  203                       	retval = nullModel;
  204                       }
  205                   }
  206               }
  207               if (retval == UNKNOWN) {
  208               	if (wrapper.isStrict()) {
  209                       throw new InvalidPropertyException("No such bean property: " + key);
  210               	} else if (logger.isDebugEnabled()) {
  211                       logNoSuchKey(key, classInfo);
  212               	}
  213           		retval = wrapper.wrap(null);
  214               }
  215               return retval;
  216           }
  217           catch(TemplateModelException e)
  218           {
  219               throw e;
  220           }
  221           catch(Exception e)
  222           {
  223               throw new TemplateModelException("get(" + key + ") failed on " +
  224                   "instance of " + object.getClass().getName(), e);
  225           }
  226       }
  227   
  228       private void logNoSuchKey(String key, Map keyMap)
  229       {
  230           logger.debug("Key '" + key + "' was not found on instance of " + 
  231               object.getClass().getName() + ". Introspection information for " +
  232               "the class is: " + keyMap);
  233       }
  234       
  235       /**
  236        * Whether the model has a plain get(String) or get(Object) method
  237        */
  238       
  239       protected boolean hasPlainGetMethod() {
  240       	return wrapper.getClassKeyMap(object.getClass()).get(BeansWrapper.GENERIC_GET_KEY) != null;
  241       }
  242       
  243       private TemplateModel invokeThroughDescriptor(Object desc, Map classInfo)
  244           throws
  245           IllegalAccessException,
  246           InvocationTargetException,
  247           TemplateModelException
  248       {
  249           // See if this particular instance has a cached implementation
  250           // for the requested feature descriptor
  251           TemplateModel member;
  252           synchronized(this) {
  253               if(memberMap != null) {
  254                   member = (TemplateModel)memberMap.get(desc);
  255               }
  256               else {
  257                   member = null;
  258               }
  259           }
  260   
  261           if(member != null)
  262               return member;
  263   
  264           TemplateModel retval = UNKNOWN;
  265           if(desc instanceof IndexedPropertyDescriptor)
  266           {
  267               Method readMethod = 
  268                   ((IndexedPropertyDescriptor)desc).getIndexedReadMethod(); 
  269               retval = member = 
  270                   new SimpleMethodModel(object, readMethod, 
  271                           BeansWrapper.getArgTypes(classInfo, readMethod), wrapper);
  272           }
  273           else if(desc instanceof PropertyDescriptor)
  274           {
  275               PropertyDescriptor pd = (PropertyDescriptor)desc;
  276               retval = wrapper.invokeMethod(object, pd.getReadMethod(), null);
  277               // (member == null) condition remains, as we don't cache these
  278           }
  279           else if(desc instanceof Field)
  280           {
  281               retval = wrapper.wrap(((Field)desc).get(object));
  282               // (member == null) condition remains, as we don't cache these
  283           }
  284           else if(desc instanceof Method)
  285           {
  286               Method method = (Method)desc;
  287               retval = member = new SimpleMethodModel(object, method, 
  288                       BeansWrapper.getArgTypes(classInfo, method), wrapper);
  289           }
  290           else if(desc instanceof MethodMap)
  291           {
  292               retval = member = 
  293                   new OverloadedMethodModel(object, (MethodMap)desc);
  294           }
  295           
  296           // If new cacheable member was created, cache it
  297           if(member != null) {
  298               synchronized(this) {
  299                   if(memberMap == null) {
  300                       memberMap = new HashMap();
  301                   }
  302                   memberMap.put(desc, member);
  303               }
  304           }
  305           return retval;
  306       }
  307   
  308       protected TemplateModel invokeGenericGet(Map keyMap, Class clazz, String key)
  309       throws
  310           IllegalAccessException,
  311           InvocationTargetException,
  312           TemplateModelException
  313       {
  314           Method genericGet = (Method)keyMap.get(BeansWrapper.GENERIC_GET_KEY);
  315           if(genericGet == null)
  316               return UNKNOWN;
  317   
  318           return wrapper.invokeMethod(object, genericGet, new Object[] { key });
  319       }
  320   
  321       protected TemplateModel wrap(Object obj)
  322       throws TemplateModelException
  323       {
  324           return wrapper.getOuterIdentity().wrap(obj);
  325       }
  326       
  327       protected Object unwrap(TemplateModel model)
  328       throws
  329           TemplateModelException
  330       {
  331           return wrapper.unwrap(model);
  332       }
  333   
  334       /**
  335        * Tells whether the model is empty. It is empty if either the wrapped 
  336        * object is null, or it is a Boolean with false value.
  337        */
  338       public boolean isEmpty()
  339       {
  340           if (object instanceof String) {
  341               return ((String) object).length() == 0;
  342           }
  343           if (object instanceof Collection) {
  344               return ((Collection) object).isEmpty();
  345           }
  346   	if (object instanceof Map) {
  347   	    return ((Map) object).isEmpty();
  348   	}
  349           return object == null || Boolean.FALSE.equals(object);
  350       }
  351       
  352       public Object getAdaptedObject(Class hint) {
  353           return object;
  354       }
  355   
  356       public Object getWrappedObject() {
  357           return object;
  358       }
  359       
  360       public int size()
  361       {
  362           return wrapper.keyCount(object.getClass());
  363       }
  364   
  365       public TemplateCollectionModel keys()
  366       {
  367           return new CollectionAndSequence(new SimpleSequence(keySet(), wrapper));
  368       }
  369   
  370       public TemplateCollectionModel values() throws TemplateModelException
  371       {
  372           List values = new ArrayList(size());
  373           TemplateModelIterator it = keys().iterator();
  374           while (it.hasNext()) {
  375               String key = ((TemplateScalarModel)it.next()).getAsString();
  376               values.add(get(key));
  377           }
  378           return new CollectionAndSequence(new SimpleSequence(values, wrapper));
  379       }
  380       
  381       public String toString() {
  382           return object.toString();
  383       }
  384   
  385       /**
  386        * Helper method to support TemplateHashModelEx. Returns the Set of
  387        * Strings which are available via the TemplateHashModel
  388        * interface. Subclasses that override <tt>invokeGenericGet</tt> to
  389        * provide additional hash keys should also override this method.
  390        */
  391       protected Set keySet()
  392       {
  393           return wrapper.keySet(object.getClass());
  394       }    
  395   }

Save This Page
Home » freemarker-2.3.13 » freemarker.ext.beans » [javadoc | source]