0001: /*
0002: * Copyright (c) 2003 The Visigoth Software Society. All rights
0003: * reserved.
0004: *
0005: * Redistribution and use in source and binary forms, with or without
0006: * modification, are permitted provided that the following conditions
0007: * are met:
0008: *
0009: * 1. Redistributions of source code must retain the above copyright
0010: * notice, this list of conditions and the following disclaimer.
0011: *
0012: * 2. Redistributions in binary form must reproduce the above copyright
0013: * notice, this list of conditions and the following disclaimer in
0014: * the documentation and/or other materials provided with the
0015: * distribution.
0016: *
0017: * 3. The end-user documentation included with the redistribution, if
0018: * any, must include the following acknowledgement:
0019: * "This product includes software developed by the
0020: * Visigoth Software Society (http://www.visigoths.org/)."
0021: * Alternately, this acknowledgement may appear in the software itself,
0022: * if and wherever such third-party acknowledgements normally appear.
0023: *
0024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
0025: * project contributors may be used to endorse or promote products derived
0026: * from this software without prior written permission. For written
0027: * permission, please contact visigoths@visigoths.org.
0028: *
0029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
0030: * nor may "FreeMarker" or "Visigoth" appear in their names
0031: * without prior written permission of the Visigoth Software Society.
0032: *
0033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
0037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0044: * SUCH DAMAGE.
0045: * ====================================================================
0046: *
0047: * This software consists of voluntary contributions made by many
0048: * individuals on behalf of the Visigoth Software Society. For more
0049: * information on the Visigoth Software Society, please see
0050: * http://www.visigoths.org/
0051: */
0052:
0053: package freemarker.ext.beans;
0054:
0055: import java.beans.BeanInfo;
0056: import java.beans.IndexedPropertyDescriptor;
0057: import java.beans.IntrospectionException;
0058: import java.beans.Introspector;
0059: import java.beans.MethodDescriptor;
0060: import java.beans.PropertyDescriptor;
0061: import java.io.InputStream;
0062: import java.lang.reflect.AccessibleObject;
0063: import java.lang.reflect.Array;
0064: import java.lang.reflect.Constructor;
0065: import java.lang.reflect.Field;
0066: import java.lang.reflect.InvocationTargetException;
0067: import java.lang.reflect.Method;
0068: import java.lang.reflect.Modifier;
0069: import java.math.BigDecimal;
0070: import java.math.BigInteger;
0071: import java.util.Arrays;
0072: import java.util.Collection;
0073: import java.util.Collections;
0074: import java.util.Date;
0075: import java.util.Enumeration;
0076: import java.util.HashMap;
0077: import java.util.HashSet;
0078: import java.util.Iterator;
0079: import java.util.List;
0080: import java.util.Map;
0081: import java.util.Properties;
0082: import java.util.ResourceBundle;
0083: import java.util.Set;
0084: import java.util.StringTokenizer;
0085:
0086: import freemarker.ext.util.IdentityHashMap;
0087: import freemarker.ext.util.ModelCache;
0088: import freemarker.ext.util.ModelFactory;
0089: import freemarker.ext.util.WrapperTemplateModel;
0090: import freemarker.log.Logger;
0091: import freemarker.template.AdapterTemplateModel;
0092: import freemarker.template.ObjectWrapper;
0093: import freemarker.template.TemplateBooleanModel;
0094: import freemarker.template.TemplateCollectionModel;
0095: import freemarker.template.TemplateDateModel;
0096: import freemarker.template.TemplateHashModel;
0097: import freemarker.template.TemplateModel;
0098: import freemarker.template.TemplateModelAdapter;
0099: import freemarker.template.TemplateModelException;
0100: import freemarker.template.TemplateNumberModel;
0101: import freemarker.template.TemplateScalarModel;
0102: import freemarker.template.TemplateSequenceModel;
0103: import freemarker.template.utility.ClassUtil;
0104: import freemarker.template.utility.Collections12;
0105: import freemarker.template.utility.SecurityUtilities;
0106: import freemarker.template.utility.UndeclaredThrowableException;
0107:
0108: /**
0109: * Utility class that provides generic services to reflection classes.
0110: * It handles all polymorphism issues in the {@link #wrap(Object)} and {@link #unwrap(TemplateModel)} methods.
0111: * @author Attila Szegedi
0112: * @version $Id: BeansWrapper.java,v 1.91.2.13 2007/04/02 13:08:59 szegedia Exp $
0113: */
0114: public class BeansWrapper implements ObjectWrapper {
0115: private static final Class BIGINTEGER_CLASS = java.math.BigInteger.class;
0116: private static final Class BOOLEAN_CLASS = Boolean.class;
0117: private static final Class CHARACTER_CLASS = Character.class;
0118: private static final Class COLLECTION_CLASS = Collection.class;
0119: private static final Class DATE_CLASS = Date.class;
0120: private static final Class HASHADAPTER_CLASS = HashAdapter.class;
0121: private static final Class ITERABLE_CLASS;
0122: private static final Class LIST_CLASS = List.class;
0123: private static final Class MAP_CLASS = Map.class;
0124: private static final Class NUMBER_CLASS = Number.class;
0125: private static final Class OBJECT_CLASS = Object.class;
0126: private static final Class SEQUENCEADAPTER_CLASS = SequenceAdapter.class;
0127: private static final Class SET_CLASS = Set.class;
0128: private static final Class SETADAPTER_CLASS = SetAdapter.class;
0129: private static final Class STRING_CLASS = String.class;
0130: static {
0131: Class iterable;
0132: try {
0133: iterable = Class.forName("java.lang.Iterable");
0134: } catch (ClassNotFoundException e) {
0135: // We're running on a pre-1.5 JRE
0136: iterable = null;
0137: }
0138: ITERABLE_CLASS = iterable;
0139: }
0140:
0141: // When this property is true, some things are stricter. This is mostly to
0142: // catch anomalous things in development that can otherwise be valid situations
0143: // for our users.
0144: private static final boolean DEVELOPMENT = "true"
0145: .equals(SecurityUtilities
0146: .getSystemProperty("freemarker.development"));
0147:
0148: private static final Constructor ENUMS_MODEL_CTOR = enumsModelCtor();
0149:
0150: private static final Logger logger = Logger
0151: .getLogger("freemarker.beans");
0152:
0153: private static final Set UNSAFE_METHODS = createUnsafeMethodsSet();
0154:
0155: static final Object GENERIC_GET_KEY = new Object();
0156: private static final Object CONSTRUCTORS = new Object();
0157: private static final Object ARGTYPES = new Object();
0158:
0159: /**
0160: * The default instance of BeansWrapper
0161: */
0162: private static final BeansWrapper INSTANCE = new BeansWrapper();
0163:
0164: // Cache of hash maps that contain already discovered properties and methods
0165: // for a specified class. Each key is a Class, each value is a hash map. In
0166: // that hash map, each key is a property/method name, each value is a
0167: // MethodDescriptor or a PropertyDescriptor assigned to that property/method.
0168: private final Map classCache = new HashMap();
0169: private Set cachedClassNames = new HashSet();
0170:
0171: private final StaticModels staticModels = new StaticModels(this );
0172: private final ClassBasedModelFactory enumModels = createEnumModels(this );
0173:
0174: private final ModelCache modelCache = new ModelCache(this );
0175:
0176: private final BooleanModel FALSE = new BooleanModel(Boolean.FALSE,
0177: this );
0178: private final BooleanModel TRUE = new BooleanModel(Boolean.TRUE,
0179: this );
0180:
0181: /**
0182: * At this level of exposure, all methods and properties of the
0183: * wrapped objects are exposed to the template.
0184: */
0185: public static final int EXPOSE_ALL = 0;
0186:
0187: /**
0188: * At this level of exposure, all methods and properties of the wrapped
0189: * objects are exposed to the template except methods that are deemed
0190: * not safe. The not safe methods are java.lang.Object methods wait() and
0191: * notify(), java.lang.Class methods getClassLoader() and newInstance(),
0192: * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and
0193: * newInstance() methods, all java.lang.reflect.Field set methods, all
0194: * java.lang.Thread and java.lang.ThreadGroup methods that can change its
0195: * state, as well as the usual suspects in java.lang.System and
0196: * java.lang.Runtime.
0197: */
0198: public static final int EXPOSE_SAFE = 1;
0199:
0200: /**
0201: * At this level of exposure, only property getters are exposed.
0202: * Additionally, property getters that map to unsafe methods are not
0203: * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
0204: */
0205: public static final int EXPOSE_PROPERTIES_ONLY = 2;
0206:
0207: /**
0208: * At this level of exposure, no bean properties and methods are exposed.
0209: * Only map items, resource bundle items, and objects retrieved through
0210: * the generic get method (on objects of classes that have a generic get
0211: * method) can be retrieved through the hash interface. You might want to
0212: * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to
0213: * speed up map item retrieval.
0214: */
0215: public static final int EXPOSE_NOTHING = 3;
0216:
0217: private int exposureLevel = EXPOSE_SAFE;
0218: private TemplateModel nullModel = null;
0219: private boolean methodsShadowItems = true;
0220: private boolean exposeFields = false;
0221: private int defaultDateType = TemplateDateModel.UNKNOWN;
0222:
0223: private ObjectWrapper outerIdentity = this ;
0224: private boolean simpleMapWrapper;
0225: private boolean strict = false;
0226:
0227: /**
0228: * Creates a new instance of BeansWrapper. The newly created instance
0229: * will use the null reference as its null object, it will use
0230: * {@link #EXPOSE_SAFE} method exposure level, and will not cache
0231: * model instances.
0232: */
0233: public BeansWrapper() {
0234: }
0235:
0236: /**
0237: * @see #setStrict(boolean)
0238: */
0239: public boolean isStrict() {
0240: return strict;
0241: }
0242:
0243: /**
0244: * Specifies if an attempt to read a bean property that doesn't exist in the
0245: * wrapped object should throw an {@link InvalidPropertyException}.
0246: *
0247: * <p>If this property is <tt>false</tt> (the default) then an attempt to read
0248: * a missing bean property is the same as reading an existing bean property whose
0249: * value is <tt>null</tt>. The template can't tell the difference, and thus always
0250: * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins
0251: * to handle the situation.
0252: *
0253: * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in
0254: * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean
0255: * object (as opposed to just holding <tt>null</tt> value) will cause
0256: * {@link InvalidPropertyException}, which can't be suppressed in the template
0257: * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way
0258: * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to
0259: * handle existing properties whose value is <tt>null</tt>, without the risk of
0260: * hiding typos in the property names. Typos will always cause error. But mind you, it
0261: * goes against the basic approach of FreeMarker, so use this feature only if you really
0262: * know what are you doing.
0263: */
0264: public void setStrict(boolean strict) {
0265: this .strict = strict;
0266: }
0267:
0268: /**
0269: * When wrapping an object, the BeansWrapper commonly needs to wrap
0270: * "sub-objects", for example each element in a wrapped collection.
0271: * Normally it wraps these objects using itself. However, this makes
0272: * it difficult to delegate to a BeansWrapper as part of a custom
0273: * aggregate ObjectWrapper. This method lets you set the ObjectWrapper
0274: * which will be used to wrap the sub-objects.
0275: * @param outerIdentity the aggregate ObjectWrapper
0276: */
0277: public void setOuterIdentity(ObjectWrapper outerIdentity) {
0278: this .outerIdentity = outerIdentity;
0279: }
0280:
0281: /**
0282: * By default returns <tt>this</tt>.
0283: * @see #setOuterIdentity(ObjectWrapper)
0284: */
0285: public ObjectWrapper getOuterIdentity() {
0286: return outerIdentity;
0287: }
0288:
0289: /**
0290: * By default the BeansWrapper wraps classes implementing
0291: * java.util.Map using {@link MapModel}. Setting this flag will
0292: * cause it to use a {@link SimpleMapModel} instead. The biggest
0293: * difference is that when using a {@link SimpleMapModel}, the
0294: * map will be visible as <code>TemplateHashModelEx</code>,
0295: * and the subvariables will be the content of the map,
0296: * without the other methods and properties of the map object.
0297: * @param simpleMapWrapper enable simple map wrapping
0298: */
0299: public void setSimpleMapWrapper(boolean simpleMapWrapper) {
0300: this .simpleMapWrapper = simpleMapWrapper;
0301: }
0302:
0303: public boolean isSimpleMapWrapper() {
0304: return simpleMapWrapper;
0305: }
0306:
0307: /**
0308: * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
0309: * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
0310: * constants.
0311: */
0312: public void setExposureLevel(int exposureLevel) {
0313: if (exposureLevel < EXPOSE_ALL
0314: || exposureLevel > EXPOSE_NOTHING) {
0315: throw new IllegalArgumentException(
0316: "Illegal exposure level " + exposureLevel);
0317: }
0318: this .exposureLevel = exposureLevel;
0319: }
0320:
0321: int getExposureLevel() {
0322: return exposureLevel;
0323: }
0324:
0325: public void setExposeFields(boolean exposeFields) {
0326: this .exposeFields = exposeFields;
0327: }
0328:
0329: public boolean isExposeFields() {
0330: return exposeFields;
0331: }
0332:
0333: /**
0334: * Sets whether methods shadow items in beans. When true (this is the
0335: * default value), <code>${object.name}</code> will first try to locate
0336: * a bean method or property with the specified name on the object, and
0337: * only if it doesn't find it will it try to call
0338: * <code>object.get(name)</code>, the so-called "generic get method" that
0339: * is usually used to access items of a container (i.e. elements of a map).
0340: * When set to false, the lookup order is reversed and generic get method
0341: * is called first, and only if it returns null is method lookup attempted.
0342: */
0343: public synchronized void setMethodsShadowItems(
0344: boolean methodsShadowItems) {
0345: this .methodsShadowItems = methodsShadowItems;
0346: }
0347:
0348: boolean isMethodsShadowItems() {
0349: return methodsShadowItems;
0350: }
0351:
0352: /**
0353: * Sets the default date type to use for date models that result from
0354: * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
0355: * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is
0356: * {@link TemplateDateModel#UNKNOWN}.
0357: * @param defaultDateType the new default date type.
0358: */
0359: public synchronized void setDefaultDateType(int defaultDateType) {
0360: this .defaultDateType = defaultDateType;
0361: }
0362:
0363: protected int getDefaultDateType() {
0364: return defaultDateType;
0365: }
0366:
0367: /**
0368: * Sets whether this wrapper caches model instances. Default is false.
0369: * When set to true, calling {@link #wrap(Object)} multiple times for
0370: * the same object will likely return the same model (although there is
0371: * no guarantee as the cache items can be cleared anytime).
0372: */
0373: public void setUseCache(boolean useCache) {
0374: modelCache.setUseCache(useCache);
0375: }
0376:
0377: /**
0378: * Sets the null model. This model is returned from the
0379: * {@link #wrap(Object)} method whenever the underlying object
0380: * reference is null. It defaults to null reference, which is dealt
0381: * with quite strictly on engine level, however you can substitute an
0382: * arbitrary (perhaps more lenient) model, such as
0383: * {@link freemarker.template.TemplateScalarModel#EMPTY_STRING}.
0384: */
0385: public void setNullModel(TemplateModel nullModel) {
0386: this .nullModel = nullModel;
0387: }
0388:
0389: /**
0390: * Returns the default instance of the wrapper. This instance is used
0391: * when you construct various bean models without explicitly specifying
0392: * a wrapper. It is also returned by
0393: * {@link freemarker.template.ObjectWrapper#BEANS_WRAPPER}
0394: * and this is the sole instance that is used by the JSP adapter.
0395: * You can modify the properties of the default instance (caching,
0396: * exposure level, null model) to affect its operation. By default, the
0397: * default instance is not caching, uses the <code>EXPOSE_SAFE</code>
0398: * exposure level, and uses null reference as the null model.
0399: */
0400: public static final BeansWrapper getDefaultInstance() {
0401: return INSTANCE;
0402: }
0403:
0404: /**
0405: * Wraps the object with a template model that is most specific for the object's
0406: * class. Specifically:
0407: * <ul>
0408: * <li>if the object is null, returns the {@link #setNullModel(TemplateModel) null model},</li>
0409: * <li>if the object is a Number returns a {@link NumberModel} for it,</li>
0410: * <li>if the object is a Date returns a {@link DateModel} for it,</li>
0411: * <li>if the object is a Boolean returns
0412: * {@link freemarker.template.TemplateBooleanModel#TRUE} or
0413: * {@link freemarker.template.TemplateBooleanModel#FALSE}</li>
0414: * <li>if the object is already a TemplateModel, returns it unchanged,</li>
0415: * <li>if the object is an array, returns a {@link ArrayModel} for it
0416: * <li>if the object is a Map, returns a {@link MapModel} for it
0417: * <li>if the object is a Collection, returns a {@link CollectionModel} for it
0418: * <li>if the object is an Iterator, returns a {@link IteratorModel} for it
0419: * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it
0420: * <li>if the object is a String, returns a {@link StringModel} for it
0421: * <li>otherwise, returns a generic {@link BeanModel} for it.
0422: * </ul>
0423: */
0424: public TemplateModel wrap(Object object)
0425: throws TemplateModelException {
0426: if (object == null)
0427: return nullModel;
0428: if (object instanceof TemplateModel)
0429: return (TemplateModel) object;
0430: if (object instanceof TemplateModelAdapter)
0431: return ((TemplateModelAdapter) object).getTemplateModel();
0432: if (object instanceof Map)
0433: return modelCache.getInstance(object,
0434: simpleMapWrapper ? SimpleMapModel.FACTORY
0435: : MapModel.FACTORY);
0436: if (object instanceof Collection)
0437: return modelCache.getInstance(object,
0438: CollectionModel.FACTORY);
0439: if (object.getClass().isArray())
0440: return modelCache.getInstance(object, ArrayModel.FACTORY);
0441: if (object instanceof Number)
0442: return modelCache.getInstance(object, NumberModel.FACTORY);
0443: if (object instanceof Date)
0444: return modelCache.getInstance(object, DateModel.FACTORY);
0445: if (object instanceof Boolean)
0446: return ((Boolean) object).booleanValue() ? TRUE : FALSE;
0447: if (object instanceof ResourceBundle)
0448: return modelCache.getInstance(object,
0449: ResourceBundleModel.FACTORY);
0450: if (object instanceof Iterator)
0451: return new IteratorModel((Iterator) object, this );
0452: if (object instanceof Enumeration)
0453: return new EnumerationModel((Enumeration) object, this );
0454: return modelCache.getInstance(object, StringModel.FACTORY);
0455: }
0456:
0457: protected TemplateModel getInstance(Object object,
0458: ModelFactory factory) {
0459: return modelCache.getInstance(object, factory);
0460: }
0461:
0462: protected TemplateModel create(Object object, Object factory) {
0463: return ((ModelFactory) factory).create(object, this );
0464: }
0465:
0466: /**
0467: * Attempts to unwrap a model into underlying object. Generally, this
0468: * method is the inverse of the {@link #wrap(Object)} method. In addition
0469: * it will unwrap arbitrary {@link TemplateNumberModel} instances into
0470: * a number, arbitrary {@link TemplateDateModel} instances into a date,
0471: * {@link TemplateScalarModel} instances into a String, and
0472: * {@link TemplateBooleanModel} instances into a Boolean.
0473: * All other objects are returned unchanged.
0474: */
0475: public Object unwrap(TemplateModel model)
0476: throws TemplateModelException {
0477: return unwrap(model, OBJECT_CLASS);
0478: }
0479:
0480: public Object unwrap(TemplateModel model, Class hint)
0481: throws TemplateModelException {
0482: return unwrap(model, hint, null);
0483: }
0484:
0485: private Object unwrap(TemplateModel model, Class hint,
0486: Map recursionStops) throws TemplateModelException {
0487: if (model == nullModel) {
0488: return null;
0489: }
0490:
0491: boolean isBoolean = Boolean.TYPE == hint;
0492: boolean isChar = Character.TYPE == hint;
0493:
0494: // This is for transparent interop with other wrappers (and ourselves)
0495: // Passing the hint allows i.e. a Jython-aware method that declares a
0496: // PyObject as its argument to receive a PyObject from a JythonModel
0497: // passed as an argument to TemplateMethodModelEx etc.
0498: if (model instanceof AdapterTemplateModel) {
0499: Object adapted = ((AdapterTemplateModel) model)
0500: .getAdaptedObject(hint);
0501: if (hint.isInstance(adapted)) {
0502: return adapted;
0503: }
0504: // Attempt numeric conversion
0505: if (adapted instanceof Number
0506: && ((hint.isPrimitive() && !isChar && !isBoolean) || NUMBER_CLASS
0507: .isAssignableFrom(hint))) {
0508: Number number = convertUnwrappedNumber(hint,
0509: (Number) adapted);
0510: if (number != null) {
0511: return number;
0512: }
0513: }
0514: }
0515:
0516: if (model instanceof WrapperTemplateModel) {
0517: Object wrapped = ((WrapperTemplateModel) model)
0518: .getWrappedObject();
0519: if (hint.isInstance(wrapped)) {
0520: return wrapped;
0521: }
0522: // Attempt numeric conversion
0523: if (wrapped instanceof Number
0524: && ((hint.isPrimitive() && !isChar && !isBoolean) || NUMBER_CLASS
0525: .isAssignableFrom(hint))) {
0526: Number number = convertUnwrappedNumber(hint,
0527: (Number) wrapped);
0528: if (number != null) {
0529: return number;
0530: }
0531: }
0532: }
0533:
0534: // Translation of generic template models to POJOs. First give priority
0535: // to various model interfaces based on the hint class. This helps us
0536: // select the appropriate interface in multi-interface models when we
0537: // know what is expected as the return type.
0538:
0539: if (STRING_CLASS == hint) {
0540: if (model instanceof TemplateScalarModel) {
0541: return ((TemplateScalarModel) model).getAsString();
0542: }
0543: // String is final, so no other conversion will work
0544: throw canNotConvert(model, hint);
0545: }
0546:
0547: // Primitive numeric types & Number.class and its subclasses
0548: if ((hint.isPrimitive() && !isChar && !isBoolean)
0549: || NUMBER_CLASS.isAssignableFrom(hint)) {
0550: if (model instanceof TemplateNumberModel) {
0551: Number number = convertUnwrappedNumber(hint,
0552: ((TemplateNumberModel) model).getAsNumber());
0553: if (number != null) {
0554: return number;
0555: }
0556: }
0557: }
0558:
0559: if (isBoolean || BOOLEAN_CLASS == hint) {
0560: if (model instanceof TemplateBooleanModel) {
0561: return ((TemplateBooleanModel) model).getAsBoolean() ? Boolean.TRUE
0562: : Boolean.FALSE;
0563: }
0564: // Boolean is final, no other conversion will work
0565: throw canNotConvert(model, hint);
0566: }
0567:
0568: if (MAP_CLASS == hint) {
0569: if (model instanceof TemplateHashModel) {
0570: return new HashAdapter((TemplateHashModel) model, this );
0571: }
0572: }
0573:
0574: if (LIST_CLASS == hint) {
0575: if (model instanceof TemplateSequenceModel) {
0576: return new SequenceAdapter(
0577: (TemplateSequenceModel) model, this );
0578: }
0579: }
0580:
0581: if (SET_CLASS == hint) {
0582: if (model instanceof TemplateCollectionModel) {
0583: return new SetAdapter((TemplateCollectionModel) model,
0584: this );
0585: }
0586: }
0587:
0588: if (COLLECTION_CLASS == hint || ITERABLE_CLASS == hint) {
0589: if (model instanceof TemplateCollectionModel) {
0590: return new CollectionAdapter(
0591: (TemplateCollectionModel) model, this );
0592: }
0593: if (model instanceof TemplateSequenceModel) {
0594: return new SequenceAdapter(
0595: (TemplateSequenceModel) model, this );
0596: }
0597: }
0598:
0599: // TemplateSequenceModels can be converted to arrays
0600: if (hint.isArray()) {
0601: if (model instanceof TemplateSequenceModel) {
0602: if (recursionStops != null) {
0603: Object retval = recursionStops.get(model);
0604: if (retval != null) {
0605: return retval;
0606: }
0607: } else {
0608: recursionStops = new IdentityHashMap();
0609: }
0610: TemplateSequenceModel seq = (TemplateSequenceModel) model;
0611: Class componentType = hint.getComponentType();
0612: Object array = Array.newInstance(componentType, seq
0613: .size());
0614: recursionStops.put(model, array);
0615: try {
0616: int size = seq.size();
0617: for (int i = 0; i < size; i++) {
0618: Array.set(array, i, unwrap(model,
0619: componentType, recursionStops));
0620: }
0621: } finally {
0622: recursionStops.remove(model);
0623: }
0624: return array;
0625: }
0626: // array classes are final, no other conversion will work
0627: throw canNotConvert(model, hint);
0628: }
0629:
0630: // Allow one-char strings to be coerced to characters
0631: if (isChar || hint == CHARACTER_CLASS) {
0632: if (model instanceof TemplateScalarModel) {
0633: String s = ((TemplateScalarModel) model).getAsString();
0634: if (s.length() == 1) {
0635: return new Character(s.charAt(0));
0636: }
0637: }
0638: // Character is final, no other conversion will work
0639: throw canNotConvert(model, hint);
0640: }
0641:
0642: if (DATE_CLASS.isAssignableFrom(hint)) {
0643: if (model instanceof TemplateDateModel) {
0644: Date date = ((TemplateDateModel) model).getAsDate();
0645: if (hint.isInstance(date)) {
0646: return date;
0647: }
0648: }
0649: }
0650:
0651: // Translation of generic template models to POJOs. Since hint was of
0652: // no help initially, now use an admittedly arbitrary order of
0653: // interfaces. Note we still test for isInstance and isAssignableFrom
0654: // to guarantee we return a compatible value.
0655: if (model instanceof TemplateNumberModel) {
0656: Number number = ((TemplateNumberModel) model).getAsNumber();
0657: if (hint.isInstance(number)) {
0658: return number;
0659: }
0660: }
0661: if (model instanceof TemplateDateModel) {
0662: Date date = ((TemplateDateModel) model).getAsDate();
0663: if (hint.isInstance(date)) {
0664: return date;
0665: }
0666: }
0667: if (model instanceof TemplateScalarModel
0668: && hint.isAssignableFrom(STRING_CLASS)) {
0669: return ((TemplateScalarModel) model).getAsString();
0670: }
0671: if (model instanceof TemplateBooleanModel
0672: && hint.isAssignableFrom(BOOLEAN_CLASS)) {
0673: return ((TemplateBooleanModel) model).getAsBoolean() ? Boolean.TRUE
0674: : Boolean.FALSE;
0675: }
0676: if (model instanceof TemplateHashModel
0677: && hint.isAssignableFrom(HASHADAPTER_CLASS)) {
0678: return new HashAdapter((TemplateHashModel) model, this );
0679: }
0680: if (model instanceof TemplateSequenceModel
0681: && hint.isAssignableFrom(SEQUENCEADAPTER_CLASS)) {
0682: return new SequenceAdapter((TemplateSequenceModel) model,
0683: this );
0684: }
0685: if (model instanceof TemplateCollectionModel
0686: && hint.isAssignableFrom(SETADAPTER_CLASS)) {
0687: return new SetAdapter((TemplateCollectionModel) model, this );
0688: }
0689:
0690: // Last ditch effort - is maybe the model itself instance of the
0691: // required type?
0692: if (hint.isInstance(model)) {
0693: return model;
0694: }
0695:
0696: throw canNotConvert(model, hint);
0697: }
0698:
0699: private static Number convertUnwrappedNumber(Class hint,
0700: Number number) {
0701: if (hint == Integer.TYPE || hint == Integer.class) {
0702: return number instanceof Integer ? (Integer) number
0703: : new Integer(number.intValue());
0704: }
0705: if (hint == Long.TYPE || hint == Long.class) {
0706: return number instanceof Long ? (Long) number : new Long(
0707: number.longValue());
0708: }
0709: if (hint == Float.TYPE || hint == Float.class) {
0710: return number instanceof Float ? (Float) number
0711: : new Float(number.longValue());
0712: }
0713: if (hint == Double.TYPE || hint == Double.class) {
0714: return number instanceof Double ? (Double) number
0715: : new Double(number.longValue());
0716: }
0717: if (hint == Byte.TYPE || hint == Byte.class) {
0718: return number instanceof Byte ? (Byte) number : new Byte(
0719: number.byteValue());
0720: }
0721: if (hint == Short.TYPE || hint == Short.class) {
0722: return number instanceof Short ? (Short) number
0723: : new Short(number.shortValue());
0724: }
0725: if (hint == BigInteger.class) {
0726: return number instanceof BigInteger ? number
0727: : new BigInteger(number.toString());
0728: }
0729: if (hint == BigDecimal.class) {
0730: if (number instanceof BigDecimal) {
0731: return number;
0732: }
0733: if (number instanceof BigInteger) {
0734: return new BigDecimal((BigInteger) number);
0735: }
0736: if (number instanceof Long) {
0737: // Because we can't represent long accurately as a
0738: // double
0739: return new BigDecimal(number.toString());
0740: }
0741: return new BigDecimal(number.doubleValue());
0742: }
0743: // Handle nonstandard Number subclasses as well as directly
0744: // java.lang.Number too
0745: if (hint.isInstance(number)) {
0746: return number;
0747: }
0748: return null;
0749: }
0750:
0751: private static TemplateModelException canNotConvert(
0752: TemplateModel model, Class hint) {
0753: if (model == null) {
0754: return new TemplateModelException(
0755: "Could not convert null to " + hint.getName());
0756: }
0757: return new TemplateModelException(
0758: "Could not convert an instance of "
0759: + model.getClass().getName() + " with value ["
0760: + model.toString() + "] to " + hint.getName());
0761: }
0762:
0763: /**
0764: * Auxiliary method that unwraps arguments for a method or constructor call.
0765: * @param arguments the argument list of template models
0766: * @param argTypes the preferred types of the arguments
0767: * @return Object[] the unwrapped arguments. null if the passed list was
0768: * null.
0769: * @throws TemplateModelException if unwrapping any argument throws one
0770: */
0771: Object[] unwrapArguments(List arguments, Class[] argTypes)
0772: throws TemplateModelException {
0773: Object[] args = null;
0774: if (arguments != null) {
0775: int size = arguments.size();
0776: args = new Object[size];
0777: Iterator it = arguments.iterator();
0778: for (int i = 0; it.hasNext(); ++i) {
0779: args[i] = unwrap((TemplateModel) it.next(), argTypes[i]);
0780: }
0781: }
0782: return args;
0783: }
0784:
0785: Object[] unwrapArguments(List arguments)
0786: throws TemplateModelException {
0787: Object[] args = null;
0788: if (arguments != null) {
0789: int size = arguments.size();
0790: args = new Object[size];
0791: Iterator it = arguments.iterator();
0792: int i = 0;
0793: while (it.hasNext()) {
0794: args[i++] = unwrap((TemplateModel) it.next());
0795: }
0796: }
0797: return args;
0798: }
0799:
0800: /**
0801: * Invokes the specified method, wrapping the return value. The specialty
0802: * of this method is that if the return value is null, and the return type
0803: * of the invoked method is void, {@link TemplateModel#NOTHING} is returned.
0804: * @param object the object to invoke the method on
0805: * @param method the method to invoke
0806: * @param args the arguments to the method
0807: * @return the wrapped return value of the method.
0808: * @throws InvocationTargetException if the invoked method threw an exception
0809: * @throws IllegalAccessException if the method can't be invoked due to an
0810: * access restriction.
0811: * @throws TemplateModelException if the return value couldn't be wrapped
0812: * (this can happen if the wrapper has an outer identity or is subclassed,
0813: * and the outer identity or the subclass throws an exception. Plain
0814: * BeansWrapper never throws TemplateModelException).
0815: */
0816: TemplateModel invokeMethod(Object object, Method method,
0817: Object[] args) throws InvocationTargetException,
0818: IllegalAccessException, TemplateModelException {
0819: Object retval = method.invoke(object, args);
0820: return method.getReturnType() == Void.TYPE ? TemplateModel.NOTHING
0821: : getOuterIdentity().wrap(retval);
0822: }
0823:
0824: /**
0825: * Returns a hash model that represents the so-called class static models.
0826: * Every class static model is itself a hash through which you can call
0827: * static methods on the specified class. To obtain a static model for a
0828: * class, get the element of this hash with the fully qualified class name.
0829: * For example, if you place this hash model inside the root data model
0830: * under name "statics", you can use i.e. <code>statics["java.lang.
0831: * System"]. currentTimeMillis()</code> to call the {@link
0832: * java.lang.System#currentTimeMillis()} method.
0833: * @return a hash model whose keys are fully qualified class names, and
0834: * that returns hash models whose elements are the static models of the
0835: * classes.
0836: */
0837: public TemplateHashModel getStaticModels() {
0838: return staticModels;
0839: }
0840:
0841: /**
0842: * Returns a hash model that represents the so-called class enum models.
0843: * Every class' enum model is itself a hash through which you can access
0844: * enum value declared by the specified class, assuming that class is an
0845: * enumeration. To obtain an enum model for a class, get the element of this
0846: * hash with the fully qualified class name. For example, if you place this
0847: * hash model inside the root data model under name "enums", you can use
0848: * i.e. <code>statics["java.math.RoundingMode"].UP</code> to access the
0849: * {@link java.math.RoundingMode#UP} value.
0850: * @return a hash model whose keys are fully qualified class names, and
0851: * that returns hash models whose elements are the enum models of the
0852: * classes.
0853: * @throws UnsupportedOperationException if this method is invoked on a
0854: * pre-1.5 JRE, as Java enums aren't supported there.
0855: */
0856: public TemplateHashModel getEnumModels() {
0857: if (enumModels == null) {
0858: throw new UnsupportedOperationException(
0859: "Enums not supported on pre-1.5 JRE");
0860: }
0861: return enumModels;
0862: }
0863:
0864: public Object newInstance(Class clazz, List arguments)
0865: throws TemplateModelException {
0866: try {
0867: introspectClass(clazz);
0868: Map classInfo = (Map) classCache.get(clazz);
0869: Object ctors = classInfo.get(CONSTRUCTORS);
0870: if (ctors == null) {
0871: throw new TemplateModelException("Class "
0872: + clazz.getName()
0873: + " has no public constructors.");
0874: }
0875: Constructor ctor = null;
0876: Object[] objargs;
0877: if (ctors instanceof Constructor) {
0878: ctor = (Constructor) ctors;
0879: objargs = unwrapArguments(arguments, getArgTypes(
0880: classInfo, ctor));
0881: } else if (ctors instanceof MethodMap) {
0882: MethodMap methodMap = (MethodMap) ctors;
0883: objargs = unwrapArguments(arguments, methodMap
0884: .getUnwrapTypes(arguments));
0885: ctor = (Constructor) methodMap.getMostSpecific(objargs);
0886: } else {
0887: // Cannot happen
0888: throw new Error();
0889: }
0890: if (objargs != null) {
0891: coerceBigDecimals(ctor, objargs);
0892: }
0893: return ctor.newInstance(objargs);
0894: } catch (TemplateModelException e) {
0895: throw e;
0896: } catch (Exception e) {
0897: throw new TemplateModelException(
0898: "Could not create instance of class "
0899: + clazz.getName(), e);
0900: }
0901: }
0902:
0903: void introspectClass(Class clazz) {
0904: synchronized (classCache) {
0905: if (!classCache.containsKey(clazz)) {
0906: introspectClassInternal(clazz);
0907: }
0908: }
0909: }
0910:
0911: private void introspectClassInternal(Class clazz) {
0912: String className = clazz.getName();
0913: if (cachedClassNames.contains(className)) {
0914: if (logger.isInfoEnabled()) {
0915: logger.info("Detected a reloaded class [" + className
0916: + "]. Clearing BeansWrapper caches.");
0917: }
0918: // Class reload detected, throw away caches
0919: classCache.clear();
0920: cachedClassNames = new HashSet();
0921: synchronized (this ) {
0922: modelCache.clearCache();
0923: }
0924: staticModels.clearCache();
0925: if (enumModels != null) {
0926: enumModels.clearCache();
0927: }
0928: }
0929: classCache.put(clazz, populateClassMap(clazz));
0930: cachedClassNames.add(className);
0931: }
0932:
0933: Map getClassKeyMap(Class clazz) {
0934: Map map;
0935: synchronized (classCache) {
0936: map = (Map) classCache.get(clazz);
0937: if (map == null) {
0938: introspectClassInternal(clazz);
0939: map = (Map) classCache.get(clazz);
0940: }
0941: }
0942: return map;
0943: }
0944:
0945: /**
0946: * Returns the number of introspected methods/properties that should
0947: * be available via the TemplateHashModel interface. Affected by the
0948: * {@link #setMethodsShadowItems(boolean)} and {@link
0949: * #setExposureLevel(int)} settings.
0950: */
0951: int keyCount(Class clazz) {
0952: Map map = getClassKeyMap(clazz);
0953: int count = map.size();
0954: if (map.containsKey(CONSTRUCTORS))
0955: count--;
0956: if (map.containsKey(GENERIC_GET_KEY))
0957: count--;
0958: if (map.containsKey(ARGTYPES))
0959: count--;
0960: return count;
0961: }
0962:
0963: /**
0964: * Returns the Set of names of introspected methods/properties that
0965: * should be available via the TemplateHashModel interface. Affected
0966: * by the {@link #setMethodsShadowItems(boolean)} and {@link
0967: * #setExposureLevel(int)} settings.
0968: */
0969: Set keySet(Class clazz) {
0970: Set set = new HashSet(getClassKeyMap(clazz).keySet());
0971: set.remove(CONSTRUCTORS);
0972: set.remove(GENERIC_GET_KEY);
0973: set.remove(ARGTYPES);
0974: return set;
0975: }
0976:
0977: /**
0978: * Populates a map with property and method descriptors for a specified
0979: * class. If any property or method descriptors specifies a read method
0980: * that is not accessible, replaces it with appropriate accessible method
0981: * from a superclass or interface.
0982: */
0983: private Map populateClassMap(Class clazz) {
0984: // Populate first from bean info
0985: Map map = populateClassMapWithBeanInfo(clazz);
0986: // Next add constructors
0987: try {
0988: Constructor[] ctors = clazz.getConstructors();
0989: if (ctors.length == 1) {
0990: Constructor ctor = ctors[0];
0991: map.put(CONSTRUCTORS, ctor);
0992: getArgTypes(map).put(ctor, ctor.getParameterTypes());
0993: } else if (ctors.length > 1) {
0994: MethodMap ctorMap = new MethodMap("<init>");
0995: for (int i = 0; i < ctors.length; i++) {
0996: ctorMap.addConstructor(ctors[i]);
0997: }
0998: map.put(CONSTRUCTORS, ctorMap);
0999: }
1000: } catch (SecurityException e) {
1001: logger.warn("Canont discover constructors for class "
1002: + clazz.getName(), e);
1003: }
1004: switch (map.size()) {
1005: case 0: {
1006: map = Collections12.EMPTY_MAP;
1007: break;
1008: }
1009: case 1: {
1010: Map.Entry e = (Map.Entry) map.entrySet().iterator().next();
1011: map = Collections12.singletonMap(e.getKey(), e.getValue());
1012: break;
1013: }
1014: }
1015: return map;
1016: }
1017:
1018: private Map populateClassMapWithBeanInfo(Class clazz) {
1019: Map classMap = new HashMap();
1020: if (exposeFields) {
1021: Field[] fields = clazz.getFields();
1022: for (int i = 0; i < fields.length; i++) {
1023: Field field = fields[i];
1024: if ((field.getModifiers() & Modifier.STATIC) == 0) {
1025: classMap.put(field.getName(), field);
1026: }
1027: }
1028: }
1029: Map accessibleMethods = discoverAccessibleMethods(clazz);
1030: Method genericGet = (Method) accessibleMethods
1031: .get(MethodSignature.GET_STRING_SIGNATURE);
1032: if (genericGet == null) {
1033: genericGet = (Method) accessibleMethods
1034: .get(MethodSignature.GET_OBJECT_SIGNATURE);
1035: }
1036: if (genericGet != null) {
1037: classMap.put(GENERIC_GET_KEY, genericGet);
1038: }
1039: if (exposureLevel == EXPOSE_NOTHING) {
1040: return classMap;
1041: }
1042:
1043: try {
1044: BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
1045: PropertyDescriptor[] pda = beanInfo
1046: .getPropertyDescriptors();
1047: MethodDescriptor[] mda = beanInfo.getMethodDescriptors();
1048:
1049: for (int i = pda.length - 1; i >= 0; --i) {
1050: PropertyDescriptor pd = pda[i];
1051: if (pd instanceof IndexedPropertyDescriptor) {
1052: IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
1053: Method readMethod = ipd.getIndexedReadMethod();
1054: Method publicReadMethod = getAccessibleMethod(
1055: readMethod, accessibleMethods);
1056: if (publicReadMethod != null
1057: && isSafeMethod(publicReadMethod)) {
1058: try {
1059: if (readMethod != publicReadMethod) {
1060: ipd = new IndexedPropertyDescriptor(ipd
1061: .getName(),
1062: ipd.getReadMethod(), ipd
1063: .getWriteMethod(),
1064: publicReadMethod,
1065: ipd.getIndexedWriteMethod());
1066: }
1067: classMap.put(ipd.getName(), ipd);
1068: getArgTypes(classMap).put(
1069: publicReadMethod,
1070: publicReadMethod
1071: .getParameterTypes());
1072: } catch (IntrospectionException e) {
1073: logger
1074: .warn(
1075: "Couldn't properly perform introspection",
1076: e);
1077: }
1078: }
1079: } else {
1080: Method readMethod = pd.getReadMethod();
1081: Method publicReadMethod = getAccessibleMethod(
1082: readMethod, accessibleMethods);
1083: if (publicReadMethod != null
1084: && isSafeMethod(publicReadMethod)) {
1085: try {
1086: if (readMethod != publicReadMethod) {
1087: pd = new PropertyDescriptor(pd
1088: .getName(), publicReadMethod,
1089: pd.getWriteMethod());
1090: pd.setReadMethod(publicReadMethod);
1091: }
1092: classMap.put(pd.getName(), pd);
1093: } catch (IntrospectionException e) {
1094: logger
1095: .warn(
1096: "Couldn't properly perform introspection",
1097: e);
1098: }
1099: }
1100: }
1101: }
1102: if (exposureLevel < EXPOSE_PROPERTIES_ONLY) {
1103: for (int i = mda.length - 1; i >= 0; --i) {
1104: MethodDescriptor md = mda[i];
1105: Method method = md.getMethod();
1106: Method publicMethod = getAccessibleMethod(method,
1107: accessibleMethods);
1108: if (publicMethod != null
1109: && isSafeMethod(publicMethod)) {
1110: String name = md.getName();
1111: Object previous = classMap.get(name);
1112: if (previous instanceof Method) {
1113: // Overloaded method - replace method with a method map
1114: MethodMap methodMap = new MethodMap(name);
1115: methodMap.addMethod((Method) previous);
1116: methodMap.addMethod(publicMethod);
1117: classMap.put(name, methodMap);
1118: // remove parameter type information
1119: getArgTypes(classMap).remove(previous);
1120: } else if (previous instanceof MethodMap) {
1121: // Already overloaded method - add new overload
1122: ((MethodMap) previous)
1123: .addMethod(publicMethod);
1124: } else {
1125: // Simple method (this far)
1126: classMap.put(name, publicMethod);
1127: getArgTypes(classMap).put(publicMethod,
1128: publicMethod.getParameterTypes());
1129: }
1130: }
1131: }
1132: }
1133: return classMap;
1134: } catch (IntrospectionException e) {
1135: logger.warn("Couldn't properly perform introspection", e);
1136: return new HashMap();
1137: }
1138: }
1139:
1140: private static Map getArgTypes(Map classMap) {
1141: Map argTypes = (Map) classMap.get(ARGTYPES);
1142: if (argTypes == null) {
1143: argTypes = new HashMap();
1144: classMap.put(ARGTYPES, argTypes);
1145: }
1146: return argTypes;
1147: }
1148:
1149: static Class[] getArgTypes(Map classMap,
1150: AccessibleObject methodOrCtor) {
1151: return (Class[]) ((Map) classMap.get(ARGTYPES))
1152: .get(methodOrCtor);
1153: }
1154:
1155: private static Method getAccessibleMethod(Method m, Map accessibles) {
1156: return m == null ? null : (Method) accessibles
1157: .get(new MethodSignature(m));
1158: }
1159:
1160: boolean isSafeMethod(Method method) {
1161: return exposureLevel < EXPOSE_SAFE
1162: || !UNSAFE_METHODS.contains(method);
1163: }
1164:
1165: /**
1166: * Retrieves mapping of methods to accessible methods for a class.
1167: * In case the class is not public, retrieves methods with same
1168: * signature as its public methods from public superclasses and
1169: * interfaces (if they exist). Basically upcasts every method to the
1170: * nearest accessible method.
1171: */
1172: private static Map discoverAccessibleMethods(Class clazz) {
1173: Map map = new HashMap();
1174: discoverAccessibleMethods(clazz, map);
1175: return map;
1176: }
1177:
1178: private static void discoverAccessibleMethods(Class clazz, Map map) {
1179: if (Modifier.isPublic(clazz.getModifiers())) {
1180: try {
1181: Method[] methods = clazz.getMethods();
1182: for (int i = 0; i < methods.length; i++) {
1183: Method method = methods[i];
1184: MethodSignature sig = new MethodSignature(method);
1185: map.put(sig, method);
1186: }
1187: return;
1188: } catch (SecurityException e) {
1189: logger
1190: .warn(
1191: "Could not discover accessible methods of class "
1192: + clazz.getName()
1193: + ", attemping superclasses/interfaces.",
1194: e);
1195: // Fall through and attempt to discover superclass/interface
1196: // methods
1197: }
1198: }
1199:
1200: Class[] interfaces = clazz.getInterfaces();
1201: for (int i = 0; i < interfaces.length; i++) {
1202: discoverAccessibleMethods(interfaces[i], map);
1203: }
1204: Class super class = clazz.getSuperclass();
1205: if (super class != null) {
1206: discoverAccessibleMethods(super class, map);
1207: }
1208: }
1209:
1210: private static final class MethodSignature {
1211: private static final MethodSignature GET_STRING_SIGNATURE = new MethodSignature(
1212: "get", new Class[] { STRING_CLASS });
1213: private static final MethodSignature GET_OBJECT_SIGNATURE = new MethodSignature(
1214: "get", new Class[] { OBJECT_CLASS });
1215:
1216: private final String name;
1217: private final Class[] args;
1218:
1219: private MethodSignature(String name, Class[] args) {
1220: this .name = name;
1221: this .args = args;
1222: }
1223:
1224: MethodSignature(Method method) {
1225: this (method.getName(), method.getParameterTypes());
1226: }
1227:
1228: public boolean equals(Object o) {
1229: if (o instanceof MethodSignature) {
1230: MethodSignature ms = (MethodSignature) o;
1231: return ms.name.equals(name)
1232: && Arrays.equals(args, ms.args);
1233: }
1234: return false;
1235: }
1236:
1237: public int hashCode() {
1238: return name.hashCode() ^ args.length;
1239: }
1240: }
1241:
1242: private static final Set createUnsafeMethodsSet() {
1243: Properties props = new Properties();
1244: InputStream in = BeansWrapper.class
1245: .getResourceAsStream("unsafeMethods.txt");
1246: if (in != null) {
1247: String methodSpec = null;
1248: try {
1249: try {
1250: props.load(in);
1251: } finally {
1252: in.close();
1253: }
1254: Set set = new HashSet(props.size() * 4 / 3, .75f);
1255: Map primClasses = createPrimitiveClassesMap();
1256: for (Iterator iterator = props.keySet().iterator(); iterator
1257: .hasNext();) {
1258: methodSpec = (String) iterator.next();
1259: try {
1260: set
1261: .add(parseMethodSpec(methodSpec,
1262: primClasses));
1263: } catch (ClassNotFoundException e) {
1264: if (DEVELOPMENT) {
1265: throw e;
1266: }
1267: } catch (NoSuchMethodException e) {
1268: if (DEVELOPMENT) {
1269: throw e;
1270: }
1271: }
1272: }
1273: return set;
1274: } catch (Exception e) {
1275: throw new RuntimeException(
1276: "Could not load unsafe method " + methodSpec
1277: + " " + e.getClass().getName() + " "
1278: + e.getMessage());
1279: }
1280: }
1281: return Collections.EMPTY_SET;
1282: }
1283:
1284: private static Method parseMethodSpec(String methodSpec,
1285: Map primClasses) throws ClassNotFoundException,
1286: NoSuchMethodException {
1287: int brace = methodSpec.indexOf('(');
1288: int dot = methodSpec.lastIndexOf('.', brace);
1289: Class clazz = ClassUtil.forName(methodSpec.substring(0, dot));
1290: String methodName = methodSpec.substring(dot + 1, brace);
1291: String argSpec = methodSpec.substring(brace + 1, methodSpec
1292: .length() - 1);
1293: StringTokenizer tok = new StringTokenizer(argSpec, ",");
1294: int argcount = tok.countTokens();
1295: Class[] argTypes = new Class[argcount];
1296: for (int i = 0; i < argcount; i++) {
1297: String argClassName = tok.nextToken();
1298: argTypes[i] = (Class) primClasses.get(argClassName);
1299: if (argTypes[i] == null) {
1300: argTypes[i] = ClassUtil.forName(argClassName);
1301: }
1302: }
1303: return clazz.getMethod(methodName, argTypes);
1304: }
1305:
1306: private static Map createPrimitiveClassesMap() {
1307: Map map = new HashMap();
1308: map.put("boolean", Boolean.TYPE);
1309: map.put("byte", Byte.TYPE);
1310: map.put("char", Character.TYPE);
1311: map.put("short", Short.TYPE);
1312: map.put("int", Integer.TYPE);
1313: map.put("long", Long.TYPE);
1314: map.put("float", Float.TYPE);
1315: map.put("double", Double.TYPE);
1316: return map;
1317: }
1318:
1319: /**
1320: * Converts any {@link BigDecimal}s in the passed array to the type of
1321: * the corresponding formal argument of the method.
1322: */
1323: public static void coerceBigDecimals(AccessibleObject callable,
1324: Object[] args) {
1325: Class[] formalTypes = null;
1326: for (int i = 0, l = args.length; i < l; ++i) {
1327: Object arg = args[i];
1328: if (arg instanceof BigDecimal) {
1329: BigDecimal bd = (BigDecimal) arg;
1330: if (formalTypes == null) {
1331: if (callable instanceof Method) {
1332: formalTypes = ((Method) callable)
1333: .getParameterTypes();
1334: } else if (callable instanceof Constructor) {
1335: formalTypes = ((Constructor) callable)
1336: .getParameterTypes();
1337: } else {
1338: // Cannot happen
1339: throw new Error();
1340: }
1341: if (formalTypes.length != l) {
1342: // This will die anyway due to incorrect number of
1343: // arguments, so there's no point in checking
1344: return;
1345: }
1346: }
1347: Class formalType = formalTypes[i];
1348: // int is expected in most situations, so we check it first
1349: if (formalType == Integer.TYPE
1350: || formalType == Integer.class) {
1351: args[i] = new Integer(bd.intValue());
1352: } else if (formalType == Double.TYPE
1353: || formalType == Double.class) {
1354: args[i] = new Double(bd.doubleValue());
1355: } else if (formalType == Long.TYPE
1356: || formalType == Long.class) {
1357: args[i] = new Long(bd.longValue());
1358: } else if (formalType == Float.TYPE
1359: || formalType == Float.class) {
1360: args[i] = new Float(bd.floatValue());
1361: } else if (formalType == Short.TYPE
1362: || formalType == Short.class) {
1363: args[i] = new Short(bd.shortValue());
1364: } else if (formalType == Byte.TYPE
1365: || formalType == Byte.class) {
1366: args[i] = new Byte(bd.byteValue());
1367: } else if (BIGINTEGER_CLASS
1368: .isAssignableFrom(formalType)) {
1369: args[i] = bd.toBigInteger();
1370: }
1371: }
1372: }
1373: }
1374:
1375: private static ClassBasedModelFactory createEnumModels(
1376: BeansWrapper wrapper) {
1377: if (ENUMS_MODEL_CTOR != null) {
1378: try {
1379: return (ClassBasedModelFactory) ENUMS_MODEL_CTOR
1380: .newInstance(new Object[] { wrapper });
1381: } catch (Exception e) {
1382: throw new UndeclaredThrowableException(e);
1383: }
1384: } else {
1385: return null;
1386: }
1387: }
1388:
1389: private static Constructor enumsModelCtor() {
1390: try {
1391: // Check if Enums are available on this platform
1392: Class.forName("java.lang.Enum");
1393: // If they are, return the appropriate constructor for enum models
1394: return Class.forName("freemarker.ext.beans.EnumModels")
1395: .getDeclaredConstructor(
1396: new Class[] { BeansWrapper.class });
1397: } catch (Exception e) {
1398: // Otherwise, return null
1399: return null;
1400: }
1401: }
1402: }
|