http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapper.java deleted file mode 100644 index 2bc7902..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapper.java +++ /dev/null @@ -1,1644 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.beans.PropertyDescriptor; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.Set; - -import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core.Version; -import org.apache.freemarker.core._CoreAPI; -import org.apache.freemarker.core._CoreLogs; -import org.apache.freemarker.core._DelayedFTLTypeDescription; -import org.apache.freemarker.core._DelayedShortClassName; -import org.apache.freemarker.core._TemplateModelException; -import org.apache.freemarker.core.model.AdapterTemplateModel; -import org.apache.freemarker.core.model.ObjectWrapper; -import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; -import org.apache.freemarker.core.model.RichObjectWrapper; -import org.apache.freemarker.core.model.TemplateBooleanModel; -import org.apache.freemarker.core.model.TemplateCollectionModel; -import org.apache.freemarker.core.model.TemplateDateModel; -import org.apache.freemarker.core.model.TemplateHashModel; -import org.apache.freemarker.core.model.TemplateMethodModelEx; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelAdapter; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateNumberModel; -import org.apache.freemarker.core.model.TemplateScalarModel; -import org.apache.freemarker.core.model.TemplateSequenceModel; -import org.apache.freemarker.core.model.WrapperTemplateModel; -import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; -import org.apache.freemarker.core.util.BugException; -import org.apache.freemarker.core.util.UndeclaredThrowableException; -import org.apache.freemarker.core.util.WriteProtectable; -import org.apache.freemarker.core.util._ClassUtil; -import org.slf4j.Logger; - -/** - * {@link ObjectWrapper} that is able to expose the Java API of arbitrary Java objects. This is also the superclass of - * {@link DefaultObjectWrapper}. Note that instances of this class generally should be created with a - * {@link BeansWrapperBuilder}, not with its public constructors. - * - * <p> - * As of 2.3.22, using {@link BeansWrapper} unextended is not recommended. Instead, {@link DefaultObjectWrapper} with - * its {@code incompatibleImprovements} property set to 2.3.22 (or higher) is the recommended {@link ObjectWrapper}. - * - * <p> - * This class is only thread-safe after you have finished calling its setter methods, and then safely published it (see - * JSR 133 and related literature). When used as part of {@link Configuration}, of course it's enough if that was safely - * published and then left unmodified. Using {@link BeansWrapperBuilder} also guarantees thread safety. - */ -public class BeansWrapper implements RichObjectWrapper, WriteProtectable { - - private static final Logger LOG = _CoreLogs.BEANS_WRAPPER; - - private static final Constructor<?> ENUMS_MODEL_CTOR = enumsModelCtor(); - - /** - * At this level of exposure, all methods and properties of the - * wrapped objects are exposed to the template. - */ - public static final int EXPOSE_ALL = 0; - - /** - * At this level of exposure, all methods and properties of the wrapped - * objects are exposed to the template except methods that are deemed - * not safe. The not safe methods are java.lang.Object methods wait() and - * notify(), java.lang.Class methods getClassLoader() and newInstance(), - * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and - * newInstance() methods, all java.lang.reflect.Field set methods, all - * java.lang.Thread and java.lang.ThreadGroup methods that can change its - * state, as well as the usual suspects in java.lang.System and - * java.lang.Runtime. - */ - public static final int EXPOSE_SAFE = 1; - - /** - * At this level of exposure, only property getters are exposed. - * Additionally, property getters that map to unsafe methods are not - * exposed (i.e. Class.classLoader and Thread.contextClassLoader). - */ - public static final int EXPOSE_PROPERTIES_ONLY = 2; - - /** - * At this level of exposure, no bean properties and methods are exposed. - * Only map items, resource bundle items, and objects retrieved through - * the generic get method (on objects of classes that have a generic get - * method) can be retrieved through the hash interface. You might want to - * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to - * speed up map item retrieval. - */ - public static final int EXPOSE_NOTHING = 3; - - // ----------------------------------------------------------------------------------------------------------------- - // Introspection cache: - - private final Object sharedIntrospectionLock; - - /** - * {@link Class} to class info cache. - * This object is possibly shared with other {@link BeansWrapper}-s! - * - * <p>To write this, always use {@link #replaceClassIntrospector(ClassIntrospectorBuilder)}. - * - * <p>When reading this, it's good idea to synchronize on sharedInrospectionLock when it doesn't hurt overall - * performance. In theory that's not needed, but apps might fail to keep the rules. - */ - private ClassIntrospector classIntrospector; - - /** - * {@link String} class name to {@link StaticModel} cache. - * This object only belongs to a single {@link BeansWrapper}. - * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good - * reference. - */ - private final StaticModels staticModels; - - /** - * {@link String} class name to {@link EnumerationModel} cache. - * This object only belongs to a single {@link BeansWrapper}. - * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good - * reference. - */ - private final ClassBasedModelFactory enumModels; - - /** - * Object to wrapped object cache; not used by default. - * This object only belongs to a single {@link BeansWrapper}. - */ - private final ModelCache modelCache; - - private final BooleanModel falseModel; - private final BooleanModel trueModel; - - // ----------------------------------------------------------------------------------------------------------------- - - // Why volatile: In principle it need not be volatile, but we want to catch modification attempts even if the - // object was published improperly to other threads. After all, the main goal of WriteProtectable is protecting - // things from buggy user code. - private volatile boolean writeProtected; - - private int defaultDateType; // initialized by PropertyAssignments.apply - private ObjectWrapper outerIdentity = this; - private boolean methodsShadowItems = true; - private boolean simpleMapWrapper; // initialized by PropertyAssignments.apply - private boolean strict; // initialized by PropertyAssignments.apply - - private final Version incompatibleImprovements; - - /** - * Use {@link BeansWrapperBuilder} instead of the public constructors if possible. - * The main disadvantage of using the public constructors is that the instances won't share caches. So unless having - * a private cache is your goal, don't use them. See - * - * @param incompatibleImprovements - * Sets which of the non-backward-compatible improvements should be enabled. Not {@code null}. This version number - * is the same as the FreeMarker version number with which the improvements were implemented. - * - * <p>For new projects, it's recommended to set this to the FreeMarker version that's used during the development. - * For released products that are still actively developed it's a low risk change to increase the 3rd - * version number further as FreeMarker is updated, but of course you should always check the list of effects - * below. Increasing the 2nd or 1st version number possibly mean substantial changes with higher risk of breaking - * the application, but again, see the list of effects below. - * - * <p>The reason it's separate from {@link Configuration#setIncompatibleImprovements(Version)} is that - * {@link ObjectWrapper} objects are often shared among multiple {@link Configuration}-s, so the two version - * numbers are technically independent. But it's recommended to keep those two version numbers the same. - * - * <p>The changes enabled by {@code incompatibleImprovements} are: - * <ul> - * <li> - * <p>2.3.0: No changes; this is the starting point, the version used in older projects. - * </li> - * <li> - * <p>2.3.21 (or higher): - * Several glitches were oms in <em>overloaded</em> method selection. This usually just gets - * rid of errors (like ambiguity exceptions and numerical precision loses due to bad overloaded method - * choices), still, as in some cases the method chosen can be a different one now (that was the point of - * the reworking after all), it can mean a change in the behavior of the application. The most important - * change is that the treatment of {@code null} arguments were oms, as earlier they were only seen - * applicable to parameters of type {@code Object}. Now {@code null}-s are seen to be applicable to any - * non-primitive parameters, and among those the one with the most specific type will be preferred (just - * like in Java), which is hence never the one with the {@code Object} parameter type. For more details - * about overloaded method selection changes see the version history in the FreeMarker Manual. - * </li> - * <li> - * <p>2.3.24 (or higher): - * {@link Iterator}-s were always said to be non-empty when using {@code ?has_content} and such (i.e., - * operators that check emptiness without reading any elements). Now an {@link Iterator} counts as - * empty exactly if it has no elements left. (Note that this bug has never affected basic functionality, like - * {@code <#list ...>}.) - * </li> - * </ul> - * - * <p>Note that the version will be normalized to the lowest version where the same incompatible - * {@link BeansWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might returns - * a lower version than what you have specified. - * - * @since 2.3.21 - */ - public BeansWrapper(Version incompatibleImprovements) { - this(new BeansWrapperConfiguration(incompatibleImprovements) {}, false); - // Attention! Don't don anything here, as the instance is possibly already visible to other threads through the - // model factory callbacks. - } - - /** - * Same as {@link #BeansWrapper(BeansWrapperConfiguration, boolean, boolean)} with {@code true} - * {@code finalizeConstruction} argument. - * - * @since 2.3.21 - */ - protected BeansWrapper(BeansWrapperConfiguration bwConf, boolean writeProtected) { - this(bwConf, writeProtected, true); - } - - /** - * Initializes the instance based on the the {@link BeansWrapperConfiguration} specified. - * - * @param writeProtected Makes the instance's configuration settings read-only via - * {@link WriteProtectable#writeProtect()}; this way it can use the shared class introspection cache. - * - * @param finalizeConstruction Decides if the construction is finalized now, or the caller will do some more - * adjustments on the instance and then call {@link #finalizeConstruction(boolean)} itself. - * - * @since 2.3.22 - */ - protected BeansWrapper(BeansWrapperConfiguration bwConf, boolean writeProtected, boolean finalizeConstruction) { - incompatibleImprovements = bwConf.getIncompatibleImprovements(); // normalized - - simpleMapWrapper = bwConf.isSimpleMapWrapper(); - defaultDateType = bwConf.getDefaultDateType(); - outerIdentity = bwConf.getOuterIdentity() != null ? bwConf.getOuterIdentity() : this; - strict = bwConf.isStrict(); - - if (!writeProtected) { - // As this is not a read-only BeansWrapper, the classIntrospector will be possibly replaced for a few times, - // but we need to use the same sharedInrospectionLock forever, because that's what the model factories - // synchronize on, even during the classIntrospector is being replaced. - sharedIntrospectionLock = new Object(); - classIntrospector = new ClassIntrospector(bwConf.classIntrospectorFactory, sharedIntrospectionLock); - } else { - // As this is a read-only BeansWrapper, the classIntrospector is never replaced, and since it's shared by - // other BeansWrapper instances, we use the lock belonging to the shared ClassIntrospector. - classIntrospector = bwConf.classIntrospectorFactory.build(); - sharedIntrospectionLock = classIntrospector.getSharedLock(); - } - - falseModel = new BooleanModel(Boolean.FALSE, this); - trueModel = new BooleanModel(Boolean.TRUE, this); - - staticModels = new StaticModels(BeansWrapper.this); - enumModels = createEnumModels(BeansWrapper.this); - modelCache = new BeansModelCache(BeansWrapper.this); - setUseModelCache(bwConf.getUseModelCache()); - - finalizeConstruction(writeProtected); - } - - /** - * Meant to be called after {@link BeansWrapper#BeansWrapper(BeansWrapperConfiguration, boolean, boolean)} when - * its last argument was {@code false}; makes the instance read-only if necessary, then registers the model - * factories in the class introspector. No further changes should be done after calling this, if - * {@code writeProtected} was {@code true}. - * - * @since 2.3.22 - */ - protected void finalizeConstruction(boolean writeProtected) { - if (writeProtected) { - writeProtect(); - } - - // Attention! At this point, the BeansWrapper must be fully initialized, as when the model factories are - // registered below, the BeansWrapper can immediately get concurrent callbacks. That those other threads will - // see consistent image of the BeansWrapper is ensured that callbacks are always sync-ed on - // classIntrospector.sharedLock, and so is classIntrospector.registerModelFactory(...). - - registerModelFactories(); - } - - /** - * Makes the configuration properties (settings) of this {@link BeansWrapper} object read-only. As changing them - * after the object has become visible to multiple threads leads to undefined behavior, it's recommended to call - * this when you have finished configuring the object. - * - * <p>Consider using {@link BeansWrapperBuilder} instead, which gives an instance that's already - * write protected and also uses some shared caches/pools. - * - * @since 2.3.21 - */ - @Override - public void writeProtect() { - writeProtected = true; - } - - /** - * @since 2.3.21 - */ - @Override - public boolean isWriteProtected() { - return writeProtected; - } - - Object getSharedIntrospectionLock() { - return sharedIntrospectionLock; - } - - /** - * If this object is already read-only according to {@link WriteProtectable}, throws {@link IllegalStateException}, - * otherwise does nothing. - * - * @since 2.3.21 - */ - protected void checkModifiable() { - if (writeProtected) throw new IllegalStateException( - "Can't modify the " + getClass().getName() + " object, as it was write protected."); - } - - /** - * @see #setStrict(boolean) - */ - public boolean isStrict() { - return strict; - } - - /** - * Specifies if an attempt to read a bean property that doesn't exist in the - * wrapped object should throw an {@link InvalidPropertyException}. - * - * <p>If this property is <tt>false</tt> (the default) then an attempt to read - * a missing bean property is the same as reading an existing bean property whose - * value is <tt>null</tt>. The template can't tell the difference, and thus always - * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins - * to handle the situation. - * - * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in - * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean - * object (as opposed to just holding <tt>null</tt> value) will cause - * {@link InvalidPropertyException}, which can't be suppressed in the template - * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way - * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to - * handle existing properties whose value is <tt>null</tt>, without the risk of - * hiding typos in the property names. Typos will always cause error. But mind you, it - * goes against the basic approach of FreeMarker, so use this feature only if you really - * know what you are doing. - */ - public void setStrict(boolean strict) { - checkModifiable(); - this.strict = strict; - } - - /** - * When wrapping an object, the BeansWrapper commonly needs to wrap - * "sub-objects", for example each element in a wrapped collection. - * Normally it wraps these objects using itself. However, this makes - * it difficult to delegate to a BeansWrapper as part of a custom - * aggregate ObjectWrapper. This method lets you set the ObjectWrapper - * which will be used to wrap the sub-objects. - * @param outerIdentity the aggregate ObjectWrapper - */ - public void setOuterIdentity(ObjectWrapper outerIdentity) { - checkModifiable(); - this.outerIdentity = outerIdentity; - } - - /** - * By default returns <tt>this</tt>. - * @see #setOuterIdentity(ObjectWrapper) - */ - public ObjectWrapper getOuterIdentity() { - return outerIdentity; - } - - /** - * When set to {@code true}, the keys in {@link Map}-s won't mix with the method names when looking at them - * from templates. The default is {@code false} for backward-compatibility, but is not recommended. - * - * <p>When this is {@code false}, {@code myMap.foo} or {@code myMap['foo']} either returns the method {@code foo}, - * or calls {@code Map.get("foo")}. If both exists (the method and the {@link Map} key), one will hide the other, - * depending on the {@link #isMethodsShadowItems()}, which default to {@code true} (the method - * wins). Some frameworks use this so that you can call {@code myMap.get(nonStringKey)} from templates [*], but it - * comes on the cost of polluting the key-set with the method names, and risking methods accidentally hiding - * {@link Map} entries (or the other way around). Thus, this setup is not recommended. - * (Technical note: {@link Map}-s will be wrapped into {@link MapModel} in this case.) - * - * <p>When this is {@code true}, {@code myMap.foo} or {@code myMap['foo']} always calls {@code Map.get("foo")}. - * The methods of the {@link Map} object aren't visible from templates in this case. This, however, spoils the - * {@code myMap.get(nonStringKey)} workaround. But now you can use {@code myMap(nonStringKey)} instead, that is, you - * can use the map itself as the {@code get} method. - * (Technical note: {@link Map}-s will be wrapped into {@link SimpleMapModel} in this case.) - * - * <p>*: For historical reasons, FreeMarker 2.3.X doesn't support non-string keys with the {@code []} operator, - * hence the workarounds. This will be likely oms in FreeMarker 2.4.0. Also note that the method- and - * the "field"-namespaces aren't separate in FreeMarker, hence {@code myMap.get} can return the {@code get} - * method. - */ - public void setSimpleMapWrapper(boolean simpleMapWrapper) { - checkModifiable(); - this.simpleMapWrapper = simpleMapWrapper; - } - - /** - * Tells whether Maps are exposed as simple maps, without access to their - * method. See {@link #setSimpleMapWrapper(boolean)} for details. - * @return true if Maps are exposed as simple hashes, false if they're - * exposed as full JavaBeans. - */ - public boolean isSimpleMapWrapper() { - return simpleMapWrapper; - } - - // I have commented this out, as it won't be in 2.3.20 yet. - /* - /** - * Tells which non-backward-compatible overloaded method selection fixes to apply; - * see {@link #setOverloadedMethodSelection(Version)}. - * / - public Version getOverloadedMethodSelection() { - return overloadedMethodSelection; - } - - /** - * Sets which non-backward-compatible overloaded method selection fixes to apply. - * This has similar logic as {@link Configuration#setIncompatibleImprovements(Version)}, - * but only applies to this aspect. - * - * Currently significant values: - * <ul> - * <li>2.3.21: Completetlly rewritten overloaded method selection, fixes several issues with the old one.</li> - * </ul> - * / - public void setOverloadedMethodSelection(Version version) { - overloadedMethodSelection = version; - } - */ - - /** - * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>. - * @param exposureLevel can be any of the <code>EXPOSE_xxx</code> - * constants. - */ - public void setExposureLevel(int exposureLevel) { - checkModifiable(); - - if (classIntrospector.getExposureLevel() != exposureLevel) { - ClassIntrospectorBuilder pa = classIntrospector.getPropertyAssignments(); - pa.setExposureLevel(exposureLevel); - replaceClassIntrospector(pa); - } - } - - /** - * @since 2.3.21 - */ - public int getExposureLevel() { - return classIntrospector.getExposureLevel(); - } - - /** - * Controls whether public instance fields of classes are exposed to - * templates. - * @param exposeFields if set to true, public instance fields of classes - * that do not have a property getter defined can be accessed directly by - * their name. If there is a property getter for a property of the same - * name as the field (i.e. getter "getFoo()" and field "foo"), then - * referring to "foo" in template invokes the getter. If set to false, no - * access to public instance fields of classes is given. Default is false. - */ - public void setExposeFields(boolean exposeFields) { - checkModifiable(); - - if (classIntrospector.getExposeFields() != exposeFields) { - ClassIntrospectorBuilder pa = classIntrospector.getPropertyAssignments(); - pa.setExposeFields(exposeFields); - replaceClassIntrospector(pa); - } - } - - /** - * Returns whether exposure of public instance fields of classes is - * enabled. See {@link #setExposeFields(boolean)} for details. - * @return true if public instance fields are exposed, false otherwise. - */ - public boolean isExposeFields() { - return classIntrospector.getExposeFields(); - } - - public MethodAppearanceFineTuner getMethodAppearanceFineTuner() { - return classIntrospector.getMethodAppearanceFineTuner(); - } - - /** - * Used to tweak certain aspects of how methods appear in the data-model; - * see {@link MethodAppearanceFineTuner} for more. - */ - public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) { - checkModifiable(); - - if (classIntrospector.getMethodAppearanceFineTuner() != methodAppearanceFineTuner) { - ClassIntrospectorBuilder pa = classIntrospector.getPropertyAssignments(); - pa.setMethodAppearanceFineTuner(methodAppearanceFineTuner); - replaceClassIntrospector(pa); - } - } - - MethodSorter getMethodSorter() { - return classIntrospector.getMethodSorter(); - } - - void setMethodSorter(MethodSorter methodSorter) { - checkModifiable(); - - if (classIntrospector.getMethodSorter() != methodSorter) { - ClassIntrospectorBuilder pa = classIntrospector.getPropertyAssignments(); - pa.setMethodSorter(methodSorter); - replaceClassIntrospector(pa); - } - } - - /** - * Tells if this instance acts like if its class introspection cache is sharable with other {@link BeansWrapper}-s. - * A restricted cache denies certain too "antisocial" operations, like {@link #clearClassIntrospecitonCache()}. - * The value depends on how the instance - * was created; with a public constructor (then this is {@code false}), or with {@link BeansWrapperBuilder} - * (then it's {@code true}). Note that in the last case it's possible that the introspection cache - * will not be actually shared because there's no one to share with, but this will {@code true} even then. - * - * @since 2.3.21 - */ - public boolean isClassIntrospectionCacheRestricted() { - return classIntrospector.getHasSharedInstanceRestrictons(); - } - - /** - * Replaces the value of {@link #classIntrospector}, but first it unregisters - * the model factories in the old {@link #classIntrospector}. - */ - private void replaceClassIntrospector(ClassIntrospectorBuilder pa) { - checkModifiable(); - - final ClassIntrospector newCI = new ClassIntrospector(pa, sharedIntrospectionLock); - final ClassIntrospector oldCI; - - // In principle this need not be synchronized, but as apps might publish the configuration improperly, or - // even modify the wrapper after publishing. This doesn't give 100% protection from those violations, - // as classIntrospector reading aren't everywhere synchronized for performance reasons. It still decreases the - // chance of accidents, because some ops on classIntrospector are synchronized, and because it will at least - // push the new value into the common shared memory. - synchronized (sharedIntrospectionLock) { - oldCI = classIntrospector; - if (oldCI != null) { - // Note that after unregistering the model factory might still gets some callback from the old - // classIntrospector - if (staticModels != null) { - oldCI.unregisterModelFactory(staticModels); - staticModels.clearCache(); - } - if (enumModels != null) { - oldCI.unregisterModelFactory(enumModels); - enumModels.clearCache(); - } - if (modelCache != null) { - oldCI.unregisterModelFactory(modelCache); - modelCache.clearCache(); - } - if (trueModel != null) { - trueModel.clearMemberCache(); - } - if (falseModel != null) { - falseModel.clearMemberCache(); - } - } - - classIntrospector = newCI; - - registerModelFactories(); - } - } - - private void registerModelFactories() { - if (staticModels != null) { - classIntrospector.registerModelFactory(staticModels); - } - if (enumModels != null) { - classIntrospector.registerModelFactory(enumModels); - } - if (modelCache != null) { - classIntrospector.registerModelFactory(modelCache); - } - } - - /** - * Sets whether methods shadow items in beans. When true (this is the - * default value), <code>${object.name}</code> will first try to locate - * a bean method or property with the specified name on the object, and - * only if it doesn't find it will it try to call - * <code>object.get(name)</code>, the so-called "generic get method" that - * is usually used to access items of a container (i.e. elements of a map). - * When set to false, the lookup order is reversed and generic get method - * is called first, and only if it returns null is method lookup attempted. - */ - public void setMethodsShadowItems(boolean methodsShadowItems) { - // This sync is here as this method was originally synchronized, but was never truly thread-safe, so I don't - // want to advertise it in the javadoc, nor I wanted to break any apps that work because of this accidentally. - synchronized (this) { - checkModifiable(); - this.methodsShadowItems = methodsShadowItems; - } - } - - boolean isMethodsShadowItems() { - return methodsShadowItems; - } - - /** - * Sets the default date type to use for date models that result from - * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or - * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is - * {@link TemplateDateModel#UNKNOWN}. - * @param defaultDateType the new default date type. - */ - public void setDefaultDateType(int defaultDateType) { - // This sync is here as this method was originally synchronized, but was never truly thread-safe, so I don't - // want to advertise it in the javadoc, nor I wanted to break any apps that work because of this accidentally. - synchronized (this) { - checkModifiable(); - - this.defaultDateType = defaultDateType; - } - } - - /** - * Returns the default date type. See {@link #setDefaultDateType(int)} for - * details. - * @return the default date type - */ - public int getDefaultDateType() { - return defaultDateType; - } - - /** - * Sets whether this wrapper caches the {@link TemplateModel}-s created for the Java objects that has wrapped with - * this object wrapper. Default is {@code false}. - * When set to {@code true}, calling {@link #wrap(Object)} multiple times for - * the same object will likely return the same model (although there is - * no guarantee as the cache items can be cleared any time). - */ - public void setUseModelCache(boolean useCache) { - checkModifiable(); - modelCache.setUseCache(useCache); - } - - /** - * @since 2.3.21 - */ - public boolean getUseModelCache() { - return modelCache.getUseCache(); - } - - /** - * Returns the version given with {@link #BeansWrapper(Version)}, normalized to the lowest version where a change - * has occurred. Thus, this is not necessarily the same version than that was given to the constructor. - * - * @since 2.3.21 - */ - public Version getIncompatibleImprovements() { - return incompatibleImprovements; - } - - /** - * Returns the lowest version number that is equivalent with the parameter version. - * @since 2.3.21 - */ - protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) { - _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements); - return Configuration.VERSION_3_0_0; - } - - /** - * Wraps the object with a template model that is most specific for the object's - * class. Specifically: - * <ul> - * <li>if the object is a Number returns a {@link NumberModel} for it,</li> - * <li>if the object is a Date returns a {@link DateModel} for it,</li> - * <li>if the object is a Boolean returns - * {@link org.apache.freemarker.core.model.TemplateBooleanModel#TRUE} or - * {@link org.apache.freemarker.core.model.TemplateBooleanModel#FALSE}</li> - * <li>if the object is already a TemplateModel, returns it unchanged,</li> - * <li>if the object is an array, returns a {@link ArrayModel} for it - * <li>if the object is a Map, returns a {@link MapModel} for it - * <li>if the object is a Collection, returns a {@link CollectionModel} for it - * <li>if the object is an Iterator, returns a {@link IteratorModel} for it - * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it - * <li>if the object is a String, returns a {@link StringModel} for it - * <li>otherwise, returns a generic {@link StringModel} for it. - * </ul> - */ - @Override - public TemplateModel wrap(Object object) throws TemplateModelException { - if (object == null) return null; - return modelCache.getInstance(object); - } - - /** - * Wraps a Java method so that it can be called from templates, without wrapping its parent ("this") object. The - * result is almost the same as that you would get by wrapping the parent object then getting the method from the - * resulting {@link TemplateHashModel} by name. Except, if the wrapped method is overloaded, with this method you - * explicitly select a an overload, while otherwise you would get a {@link TemplateMethodModelEx} that selects an - * overload each time it's called based on the argument values. - * - * @param object The object whose method will be called, or {@code null} if {@code method} is a static method. - * This object will be used "as is", like without unwrapping it if it's a {@link TemplateModelAdapter}. - * @param method The method to call, which must be an (inherited) member of the class of {@code object}, as - * described by {@link Method#invoke(Object, Object...)} - * - * @since 2.3.22 - */ - public TemplateMethodModelEx wrap(Object object, Method method) { - return new SimpleMethodModel(object, method, method.getParameterTypes(), this); - } - - /** - * @since 2.3.22 - */ - @Override - public TemplateHashModel wrapAsAPI(Object obj) throws TemplateModelException { - return new APIModel(obj, this); - } - - private final ModelFactory BOOLEAN_FACTORY = new ModelFactory() { - @Override - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return ((Boolean) object).booleanValue() ? trueModel : falseModel; - } - }; - - private static final ModelFactory ITERATOR_FACTORY = new ModelFactory() { - @Override - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return new IteratorModel((Iterator<?>) object, (BeansWrapper) wrapper); - } - }; - - private static final ModelFactory ENUMERATION_FACTORY = new ModelFactory() { - @Override - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return new EnumerationModel((Enumeration<?>) object, (BeansWrapper) wrapper); - } - }; - - protected ModelFactory getModelFactory(Class<?> clazz) { - if (Map.class.isAssignableFrom(clazz)) { - return simpleMapWrapper ? SimpleMapModel.FACTORY : MapModel.FACTORY; - } - if (Collection.class.isAssignableFrom(clazz)) { - return CollectionModel.FACTORY; - } - if (Number.class.isAssignableFrom(clazz)) { - return NumberModel.FACTORY; - } - if (Date.class.isAssignableFrom(clazz)) { - return DateModel.FACTORY; - } - if (Boolean.class == clazz) { // Boolean is final - return BOOLEAN_FACTORY; - } - if (ResourceBundle.class.isAssignableFrom(clazz)) { - return ResourceBundleModel.FACTORY; - } - if (Iterator.class.isAssignableFrom(clazz)) { - return ITERATOR_FACTORY; - } - if (Enumeration.class.isAssignableFrom(clazz)) { - return ENUMERATION_FACTORY; - } - if (clazz.isArray()) { - return ArrayModel.FACTORY; - } - return StringModel.FACTORY; - } - - /** - * Attempts to unwrap a model into underlying object. Generally, this - * method is the inverse of the {@link #wrap(Object)} method. In addition - * it will unwrap arbitrary {@link TemplateNumberModel} instances into - * a number, arbitrary {@link TemplateDateModel} instances into a date, - * {@link TemplateScalarModel} instances into a String, arbitrary - * {@link TemplateBooleanModel} instances into a Boolean, arbitrary - * {@link TemplateHashModel} instances into a Map, arbitrary - * {@link TemplateSequenceModel} into a List, and arbitrary - * {@link TemplateCollectionModel} into a Set. All other objects are - * returned unchanged. - * @throws TemplateModelException if an attempted unwrapping fails. - */ - @Override - public Object unwrap(TemplateModel model) throws TemplateModelException { - return unwrap(model, Object.class); - } - - /** - * Attempts to unwrap a model into an object of the desired class. - * Generally, this method is the inverse of the {@link #wrap(Object)} - * method. It recognizes a wide range of target classes - all Java built-in - * primitives, primitive wrappers, numbers, dates, sets, lists, maps, and - * native arrays. - * @param model the model to unwrap - * @param targetClass the class of the unwrapped result; {@code Object.class} of we don't know what the expected type is. - * @return the unwrapped result of the desired class - * @throws TemplateModelException if an attempted unwrapping fails. - * - * @see #tryUnwrapTo(TemplateModel, Class) - */ - public Object unwrap(TemplateModel model, Class<?> targetClass) - throws TemplateModelException { - final Object obj = tryUnwrapTo(model, targetClass); - if (obj == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { - throw new TemplateModelException("Can not unwrap model of type " + - model.getClass().getName() + " to type " + targetClass.getName()); - } - return obj; - } - - /** - * @since 2.3.22 - */ - @Override - public Object tryUnwrapTo(TemplateModel model, Class<?> targetClass) throws TemplateModelException { - return tryUnwrapTo(model, targetClass, 0); - } - - /** - * @param typeFlags - * Used when unwrapping for overloaded methods and so the {@code targetClass} is possibly too generic - * (as it's the most specific common superclass). Must be 0 when unwrapping parameter values for - * non-overloaded methods. - * @return {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} or the unwrapped object. - */ - Object tryUnwrapTo(TemplateModel model, Class<?> targetClass, int typeFlags) throws TemplateModelException { - Object res = tryUnwrapTo(model, targetClass, typeFlags, null); - if ((typeFlags & TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT) != 0 - && res instanceof Number) { - return OverloadedNumberUtil.addFallbackType((Number) res, typeFlags); - } else { - return res; - } - } - - /** - * See {@link #tryUnwrapTo(TemplateModel, Class, int)}. - */ - private Object tryUnwrapTo(final TemplateModel model, Class<?> targetClass, final int typeFlags, - final Map<Object, Object> recursionStops) - throws TemplateModelException { - if (model == null) { - return null; - } - - if (targetClass.isPrimitive()) { - targetClass = _ClassUtil.primitiveClassToBoxingClass(targetClass); - } - - // This is for transparent interop with other wrappers (and ourselves) - // Passing the targetClass allows e.g. a Jython-aware method that declares a - // PyObject as its argument to receive a PyObject from a Jython-aware TemplateModel - // passed as an argument to TemplateMethodModelEx etc. - if (model instanceof AdapterTemplateModel) { - Object wrapped = ((AdapterTemplateModel) model).getAdaptedObject(targetClass); - if (targetClass == Object.class || targetClass.isInstance(wrapped)) { - return wrapped; - } - - // Attempt numeric conversion: - if (targetClass != Object.class && (wrapped instanceof Number && _ClassUtil.isNumerical(targetClass))) { - Number number = forceUnwrappedNumberToType((Number) wrapped, targetClass); - if (number != null) return number; - } - } - - if (model instanceof WrapperTemplateModel) { - Object wrapped = ((WrapperTemplateModel) model).getWrappedObject(); - if (targetClass == Object.class || targetClass.isInstance(wrapped)) { - return wrapped; - } - - // Attempt numeric conversion: - if (targetClass != Object.class && (wrapped instanceof Number && _ClassUtil.isNumerical(targetClass))) { - Number number = forceUnwrappedNumberToType((Number) wrapped, targetClass); - if (number != null) { - return number; - } - } - } - - // Translation of generic template models to POJOs. First give priority - // to various model interfaces based on the targetClass. This helps us - // select the appropriate interface in multi-interface models when we - // know what is expected as the return type. - if (targetClass != Object.class) { - - // [2.4][IcI]: Should also check for CharSequence at the end - if (String.class == targetClass) { - if (model instanceof TemplateScalarModel) { - return ((TemplateScalarModel) model).getAsString(); - } - // String is final, so no other conversion will work - return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; - } - - // Primitive numeric types & Number.class and its subclasses - if (_ClassUtil.isNumerical(targetClass)) { - if (model instanceof TemplateNumberModel) { - Number number = forceUnwrappedNumberToType( - ((TemplateNumberModel) model).getAsNumber(), targetClass); - if (number != null) { - return number; - } - } - } - - if (boolean.class == targetClass || Boolean.class == targetClass) { - if (model instanceof TemplateBooleanModel) { - return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean()); - } - // Boolean is final, no other conversion will work - return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; - } - - if (Map.class == targetClass) { - if (model instanceof TemplateHashModel) { - return new HashAdapter((TemplateHashModel) model, this); - } - } - - if (List.class == targetClass) { - if (model instanceof TemplateSequenceModel) { - return new SequenceAdapter((TemplateSequenceModel) model, this); - } - } - - if (Set.class == targetClass) { - if (model instanceof TemplateCollectionModel) { - return new SetAdapter((TemplateCollectionModel) model, this); - } - } - - if (Collection.class == targetClass || Iterable.class == targetClass) { - if (model instanceof TemplateCollectionModel) { - return new CollectionAdapter((TemplateCollectionModel) model, - this); - } - if (model instanceof TemplateSequenceModel) { - return new SequenceAdapter((TemplateSequenceModel) model, this); - } - } - - // TemplateSequenceModels can be converted to arrays - if (targetClass.isArray()) { - if (model instanceof TemplateSequenceModel) { - return unwrapSequenceToArray((TemplateSequenceModel) model, targetClass, true, recursionStops); - } - // array classes are final, no other conversion will work - return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; - } - - // Allow one-char strings to be coerced to characters - if (char.class == targetClass || targetClass == Character.class) { - if (model instanceof TemplateScalarModel) { - String s = ((TemplateScalarModel) model).getAsString(); - if (s.length() == 1) { - return Character.valueOf(s.charAt(0)); - } - } - // Character is final, no other conversion will work - return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; - } - - if (Date.class.isAssignableFrom(targetClass) && model instanceof TemplateDateModel) { - Date date = ((TemplateDateModel) model).getAsDate(); - if (targetClass.isInstance(date)) { - return date; - } - } - } // End: if (targetClass != Object.class) - - // Since the targetClass was of no help initially, now we use - // a quite arbitrary order in which we walk through the TemplateModel subinterfaces, and unwrapp them to - // their "natural" Java correspondent. We still try exclude unwrappings that won't fit the target parameter - // type(s). This is mostly important because of multi-typed FTL values that could be unwrapped on multiple ways. - int itf = typeFlags; // Iteration's Type Flags. Should be always 0 for non-overloaded and when !is2321Bugfixed. - // If itf != 0, we possibly execute the following loop body at twice: once with utilizing itf, and if it has not - // returned, once more with itf == 0. Otherwise we execute this once with itf == 0. - do { - if ((itf == 0 || (itf & TypeFlags.ACCEPTS_NUMBER) != 0) - && model instanceof TemplateNumberModel) { - Number number = ((TemplateNumberModel) model).getAsNumber(); - if (itf != 0 || targetClass.isInstance(number)) { - return number; - } - } - if ((itf == 0 || (itf & TypeFlags.ACCEPTS_DATE) != 0) - && model instanceof TemplateDateModel) { - Date date = ((TemplateDateModel) model).getAsDate(); - if (itf != 0 || targetClass.isInstance(date)) { - return date; - } - } - if ((itf == 0 || (itf & (TypeFlags.ACCEPTS_STRING | TypeFlags.CHARACTER)) != 0) - && model instanceof TemplateScalarModel - && (itf != 0 || targetClass.isAssignableFrom(String.class))) { - String strVal = ((TemplateScalarModel) model).getAsString(); - if (itf == 0 || (itf & TypeFlags.CHARACTER) == 0) { - return strVal; - } else { // TypeFlags.CHAR == 1 - if (strVal.length() == 1) { - if ((itf & TypeFlags.ACCEPTS_STRING) != 0) { - return new CharacterOrString(strVal); - } else { - return Character.valueOf(strVal.charAt(0)); - } - } else if ((itf & TypeFlags.ACCEPTS_STRING) != 0) { - return strVal; - } - // It had to be unwrapped to Character, but the string length wasn't 1 => Fall through - } - } - // Should be earlier than TemplateScalarModel, but we keep it here until FM 2.4 or such - if ((itf == 0 || (itf & TypeFlags.ACCEPTS_BOOLEAN) != 0) - && model instanceof TemplateBooleanModel - && (itf != 0 || targetClass.isAssignableFrom(Boolean.class))) { - return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean()); - } - if ((itf == 0 || (itf & TypeFlags.ACCEPTS_MAP) != 0) - && model instanceof TemplateHashModel - && (itf != 0 || targetClass.isAssignableFrom(HashAdapter.class))) { - return new HashAdapter((TemplateHashModel) model, this); - } - if ((itf == 0 || (itf & TypeFlags.ACCEPTS_LIST) != 0) - && model instanceof TemplateSequenceModel - && (itf != 0 || targetClass.isAssignableFrom(SequenceAdapter.class))) { - return new SequenceAdapter((TemplateSequenceModel) model, this); - } - if ((itf == 0 || (itf & TypeFlags.ACCEPTS_SET) != 0) - && model instanceof TemplateCollectionModel - && (itf != 0 || targetClass.isAssignableFrom(SetAdapter.class))) { - return new SetAdapter((TemplateCollectionModel) model, this); - } - - if ((itf & TypeFlags.ACCEPTS_ARRAY) != 0 - && model instanceof TemplateSequenceModel) { - return new SequenceAdapter((TemplateSequenceModel) model, this); - } - - if (itf == 0) { - break; - } - itf = 0; // start 2nd iteration - } while (true); - - // Last ditch effort - is maybe the model itself is an instance of the required type? - // Note that this will be always true for Object.class targetClass. - if (targetClass.isInstance(model)) { - return model; - } - - return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; - } - - /** - * @param tryOnly - * If {@code true}, if the conversion of an item to the component type isn't possible, the method returns - * {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} instead of throwing a - * {@link TemplateModelException}. - */ - Object unwrapSequenceToArray( - TemplateSequenceModel seq, Class<?> arrayClass, boolean tryOnly, Map<Object, Object> recursionStops) - throws TemplateModelException { - if (recursionStops != null) { - Object retval = recursionStops.get(seq); - if (retval != null) { - return retval; - } - } else { - recursionStops = new IdentityHashMap<>(); - } - Class<?> componentType = arrayClass.getComponentType(); - Object array = Array.newInstance(componentType, seq.size()); - recursionStops.put(seq, array); - try { - final int size = seq.size(); - for (int i = 0; i < size; i++) { - final TemplateModel seqItem = seq.get(i); - Object val = tryUnwrapTo(seqItem, componentType, 0, recursionStops); - if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { - if (tryOnly) { - return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; - } else { - throw new _TemplateModelException( - "Failed to convert ", new _DelayedFTLTypeDescription(seq), - " object to ", new _DelayedShortClassName(array.getClass()), - ": Problematic sequence item at index ", Integer.valueOf(i) ," with value type: ", - new _DelayedFTLTypeDescription(seqItem)); - } - - } - Array.set(array, i, val); - } - } finally { - recursionStops.remove(seq); - } - return array; - } - - Object listToArray(List<?> list, Class<?> arrayClass, Map<Object, Object> recursionStops) - throws TemplateModelException { - if (list instanceof SequenceAdapter) { - return unwrapSequenceToArray( - ((SequenceAdapter) list).getTemplateSequenceModel(), - arrayClass, false, - recursionStops); - } - - if (recursionStops != null) { - Object retval = recursionStops.get(list); - if (retval != null) { - return retval; - } - } else { - recursionStops = new IdentityHashMap<>(); - } - Class<?> componentType = arrayClass.getComponentType(); - Object array = Array.newInstance(componentType, list.size()); - recursionStops.put(list, array); - try { - boolean isComponentTypeExamined = false; - boolean isComponentTypeNumerical = false; // will be filled on demand - boolean isComponentTypeList = false; // will be filled on demand - int i = 0; - for (Object listItem : list) { - if (listItem != null && !componentType.isInstance(listItem)) { - // Type conversion is needed. If we can't do it, we just let it fail at Array.set later. - if (!isComponentTypeExamined) { - isComponentTypeNumerical = _ClassUtil.isNumerical(componentType); - isComponentTypeList = List.class.isAssignableFrom(componentType); - isComponentTypeExamined = true; - } - if (isComponentTypeNumerical && listItem instanceof Number) { - listItem = forceUnwrappedNumberToType((Number) listItem, componentType); - } else if (componentType == String.class && listItem instanceof Character) { - listItem = String.valueOf(((Character) listItem).charValue()); - } else if ((componentType == Character.class || componentType == char.class) - && listItem instanceof String) { - String listItemStr = (String) listItem; - if (listItemStr.length() == 1) { - listItem = Character.valueOf(listItemStr.charAt(0)); - } - } else if (componentType.isArray()) { - if (listItem instanceof List) { - listItem = listToArray((List<?>) listItem, componentType, recursionStops); - } else if (listItem instanceof TemplateSequenceModel) { - listItem = unwrapSequenceToArray((TemplateSequenceModel) listItem, componentType, false, recursionStops); - } - } else if (isComponentTypeList && listItem.getClass().isArray()) { - listItem = arrayToList(listItem); - } - } - try { - Array.set(array, i, listItem); - } catch (IllegalArgumentException e) { - throw new TemplateModelException( - "Failed to convert " + _ClassUtil.getShortClassNameOfObject(list) - + " object to " + _ClassUtil.getShortClassNameOfObject(array) - + ": Problematic List item at index " + i + " with value type: " - + _ClassUtil.getShortClassNameOfObject(listItem), e); - } - i++; - } - } finally { - recursionStops.remove(list); - } - return array; - } - - /** - * @param array Must be an array (of either a reference or primitive type) - */ - List<?> arrayToList(Object array) throws TemplateModelException { - if (array instanceof Object[]) { - // Array of any non-primitive type. - // Note that an array of non-primitive type is always instanceof Object[]. - Object[] objArray = (Object[]) array; - return objArray.length == 0 ? Collections.EMPTY_LIST : new NonPrimitiveArrayBackedReadOnlyList(objArray); - } else { - // Array of any primitive type - return Array.getLength(array) == 0 ? Collections.EMPTY_LIST : new PrimtiveArrayBackedReadOnlyList(array); - } - } - - /** - * Converts a number to the target type aggressively (possibly with overflow or significant loss of precision). - * @param n Non-{@code null} - * @return {@code null} if the conversion has failed. - */ - static Number forceUnwrappedNumberToType(final Number n, final Class<?> targetType) { - // We try to order the conditions by decreasing probability. - if (targetType == n.getClass()) { - return n; - } else if (targetType == int.class || targetType == Integer.class) { - return n instanceof Integer ? (Integer) n : Integer.valueOf(n.intValue()); - } else if (targetType == long.class || targetType == Long.class) { - return n instanceof Long ? (Long) n : Long.valueOf(n.longValue()); - } else if (targetType == double.class || targetType == Double.class) { - return n instanceof Double ? (Double) n : Double.valueOf(n.doubleValue()); - } else if (targetType == BigDecimal.class) { - if (n instanceof BigDecimal) { - return n; - } else if (n instanceof BigInteger) { - return new BigDecimal((BigInteger) n); - } else if (n instanceof Long) { - // Because we can't represent long accurately as double - return BigDecimal.valueOf(n.longValue()); - } else { - return new BigDecimal(n.doubleValue()); - } - } else if (targetType == float.class || targetType == Float.class) { - return n instanceof Float ? (Float) n : Float.valueOf(n.floatValue()); - } else if (targetType == byte.class || targetType == Byte.class) { - return n instanceof Byte ? (Byte) n : Byte.valueOf(n.byteValue()); - } else if (targetType == short.class || targetType == Short.class) { - return n instanceof Short ? (Short) n : Short.valueOf(n.shortValue()); - } else if (targetType == BigInteger.class) { - if (n instanceof BigInteger) { - return n; - } else { - if (n instanceof OverloadedNumberUtil.IntegerBigDecimal) { - return ((OverloadedNumberUtil.IntegerBigDecimal) n).bigIntegerValue(); - } else if (n instanceof BigDecimal) { - return ((BigDecimal) n).toBigInteger(); - } else { - return BigInteger.valueOf(n.longValue()); - } - } - } else { - final Number oriN = n instanceof OverloadedNumberUtil.NumberWithFallbackType - ? ((OverloadedNumberUtil.NumberWithFallbackType) n).getSourceNumber() : n; - if (targetType.isInstance(oriN)) { - // Handle nonstandard Number subclasses as well as directly java.lang.Number. - return oriN; - } else { - // Fails - return null; - } - } - } - - /** - * Invokes the specified method, wrapping the return value. The specialty - * of this method is that if the return value is null, and the return type - * of the invoked method is void, {@link TemplateModel#NOTHING} is returned. - * @param object the object to invoke the method on - * @param method the method to invoke - * @param args the arguments to the method - * @return the wrapped return value of the method. - * @throws InvocationTargetException if the invoked method threw an exception - * @throws IllegalAccessException if the method can't be invoked due to an - * access restriction. - * @throws TemplateModelException if the return value couldn't be wrapped - * (this can happen if the wrapper has an outer identity or is subclassed, - * and the outer identity or the subclass throws an exception. Plain - * BeansWrapper never throws TemplateModelException). - */ - TemplateModel invokeMethod(Object object, Method method, Object[] args) - throws InvocationTargetException, - IllegalAccessException, - TemplateModelException { - // [2.4]: Java's Method.invoke truncates numbers if the target type has not enough bits to hold the value. - // There should at least be an option to check this. - Object retval = method.invoke(object, args); - return - method.getReturnType() == void.class - ? TemplateModel.NOTHING - : getOuterIdentity().wrap(retval); - } - - /** - * Returns a hash model that represents the so-called class static models. - * Every class static model is itself a hash through which you can call - * static methods on the specified class. To obtain a static model for a - * class, get the element of this hash with the fully qualified class name. - * For example, if you place this hash model inside the root data model - * under name "statics", you can use i.e. <code>statics["java.lang. - * System"]. currentTimeMillis()</code> to call the {@link - * java.lang.System#currentTimeMillis()} method. - * @return a hash model whose keys are fully qualified class names, and - * that returns hash models whose elements are the static models of the - * classes. - */ - public TemplateHashModel getStaticModels() { - return staticModels; - } - - /** - * Returns a hash model that represents the so-called class enum models. - * Every class' enum model is itself a hash through which you can access - * enum value declared by the specified class, assuming that class is an - * enumeration. To obtain an enum model for a class, get the element of this - * hash with the fully qualified class name. For example, if you place this - * hash model inside the root data model under name "enums", you can use - * i.e. <code>statics["java.math.RoundingMode"].UP</code> to access the - * {@link java.math.RoundingMode#UP} value. - * @return a hash model whose keys are fully qualified class names, and - * that returns hash models whose elements are the enum models of the - * classes. - * @throws UnsupportedOperationException if this method is invoked on a - * pre-1.5 JRE, as Java enums aren't supported there. - */ - public TemplateHashModel getEnumModels() { - if (enumModels == null) { - throw new UnsupportedOperationException( - "Enums not supported before J2SE 5."); - } - return enumModels; - } - - /** For Unit tests only */ - ModelCache getModelCache() { - return modelCache; - } - - /** - * Creates a new instance of the specified class using the method call logic of this object wrapper for calling the - * constructor. Overloaded constructors and varargs are supported. Only public constructors will be called. - * - * @param clazz The class whose constructor we will call. - * @param arguments The list of {@link TemplateModel}-s to pass to the constructor after unwrapping them - * @return The instance created; it's not wrapped into {@link TemplateModel}. - */ - public Object newInstance(Class<?> clazz, List/*<? extends TemplateModel>*/ arguments) - throws TemplateModelException { - try { - Object ctors = classIntrospector.get(clazz).get(ClassIntrospector.CONSTRUCTORS_KEY); - if (ctors == null) { - throw new TemplateModelException("Class " + clazz.getName() + - " has no public constructors."); - } - Constructor<?> ctor = null; - Object[] objargs; - if (ctors instanceof SimpleMethod) { - SimpleMethod sm = (SimpleMethod) ctors; - ctor = (Constructor<?>) sm.getMember(); - objargs = sm.unwrapArguments(arguments, this); - try { - return ctor.newInstance(objargs); - } catch (Exception e) { - if (e instanceof TemplateModelException) throw (TemplateModelException) e; - throw _MethodUtil.newInvocationTemplateModelException(null, ctor, e); - } - } else if (ctors instanceof OverloadedMethods) { - final MemberAndArguments mma = ((OverloadedMethods) ctors).getMemberAndArguments(arguments, this); - try { - return mma.invokeConstructor(this); - } catch (Exception e) { - if (e instanceof TemplateModelException) throw (TemplateModelException) e; - - throw _MethodUtil.newInvocationTemplateModelException(null, mma.getCallableMemberDescriptor(), e); - } - } else { - // Cannot happen - throw new BugException(); - } - } catch (TemplateModelException e) { - throw e; - } catch (Exception e) { - throw new TemplateModelException( - "Error while creating new instance of class " + clazz.getName() + "; see cause exception", e); - } - } - - /** - * Removes the introspection data for a class from the cache. - * Use this if you know that a class is not used anymore in templates. - * If the class will be still used, the cache entry will be silently - * re-created, so this isn't a dangerous operation. - * - * @since 2.3.20 - */ - public void removeFromClassIntrospectionCache(Class<?> clazz) { - classIntrospector.remove(clazz); - } - - /** - * Removes all class introspection data from the cache. - * - * <p>Use this if you want to free up memory on the expense of recreating - * the cache entries for the classes that will be used later in templates. - * - * @throws IllegalStateException if {@link #isClassIntrospectionCacheRestricted()} is {@code true}. - * - * @since 2.3.20 - */ - public void clearClassIntrospecitonCache() { - classIntrospector.clearCache(); - } - - ClassIntrospector getClassIntrospector() { - return classIntrospector; - } - - /** - * Converts any {@link BigDecimal}s in the passed array to the type of - * the corresponding formal argument of the method. - */ - // Unused? - public static void coerceBigDecimals(AccessibleObject callable, Object[] args) { - Class<?>[] formalTypes = null; - for (int i = 0; i < args.length; ++i) { - Object arg = args[i]; - if (arg instanceof BigDecimal) { - if (formalTypes == null) { - if (callable instanceof Method) { - formalTypes = ((Method) callable).getParameterTypes(); - } else if (callable instanceof Constructor) { - formalTypes = ((Constructor<?>) callable).getParameterTypes(); - } else { - throw new IllegalArgumentException("Expected method or " - + " constructor; callable is " + - callable.getClass().getName()); - } - } - args[i] = coerceBigDecimal((BigDecimal) arg, formalTypes[i]); - } - } - } - - /** - * Converts any {@link BigDecimal}-s in the passed array to the type of - * the corresponding formal argument of the method via {@link #coerceBigDecimal(BigDecimal, Class)}. - */ - public static void coerceBigDecimals(Class<?>[] formalTypes, Object[] args) { - int typeLen = formalTypes.length; - int argsLen = args.length; - int min = Math.min(typeLen, argsLen); - for (int i = 0; i < min; ++i) { - Object arg = args[i]; - if (arg instanceof BigDecimal) { - args[i] = coerceBigDecimal((BigDecimal) arg, formalTypes[i]); - } - } - if (argsLen > typeLen) { - Class<?> varArgType = formalTypes[typeLen - 1]; - for (int i = typeLen; i < argsLen; ++i) { - Object arg = args[i]; - if (arg instanceof BigDecimal) { - args[i] = coerceBigDecimal((BigDecimal) arg, varArgType); - } - } - } - } - - /** - * Converts {@link BigDecimal} to the class given in the {@code formalType} argument if that's a known numerical - * type, returns the {@link BigDecimal} as is otherwise. Overflow and precision loss are possible, similarly as - * with casting in Java. - */ - public static Object coerceBigDecimal(BigDecimal bd, Class<?> formalType) { - // int is expected in most situations, so we check it first - if (formalType == int.class || formalType == Integer.class) { - return Integer.valueOf(bd.intValue()); - } else if (formalType == double.class || formalType == Double.class) { - return Double.valueOf(bd.doubleValue()); - } else if (formalType == long.class || formalType == Long.class) { - return Long.valueOf(bd.longValue()); - } else if (formalType == float.class || formalType == Float.class) { - return Float.valueOf(bd.floatValue()); - } else if (formalType == short.class || formalType == Short.class) { - return Short.valueOf(bd.shortValue()); - } else if (formalType == byte.class || formalType == Byte.class) { - return Byte.valueOf(bd.byteValue()); - } else if (java.math.BigInteger.class.isAssignableFrom(formalType)) { - return bd.toBigInteger(); - } else { - return bd; - } - } - - /** - * Returns the exact class name and the identity hash, also the values of the most often used {@link BeansWrapper} - * configuration properties, also if which (if any) shared class introspection cache it uses. - * - * @since 2.3.21 - */ - @Override - public String toString() { - final String propsStr = toPropertiesString(); - return _ClassUtil.getShortClassNameOfObject(this) + "@" + System.identityHashCode(this) - + "(" + incompatibleImprovements + ", " - + (propsStr.length() != 0 ? propsStr + ", ..." : "") - + ")"; - } - - /** - * Returns the name-value pairs that describe the configuration of this {@link BeansWrapper}; called from - * {@link #toString()}. The expected format is like {@code "foo=bar, baaz=wombat"}. When overriding this, you should - * call the super method, and then insert the content before it with a following {@code ", "}, or after it with a - * preceding {@code ", "}. - * - * @since 2.3.22 - */ - protected String toPropertiesString() { - // Start with "simpleMapWrapper", because the override in DefaultObjectWrapper expects it to be there! - return "simpleMapWrapper=" + simpleMapWrapper + ", " - + "exposureLevel=" + classIntrospector.getExposureLevel() + ", " - + "exposeFields=" + classIntrospector.getExposeFields() + ", " - + "sharedClassIntrospCache=" - + (classIntrospector.isShared() ? "@" + System.identityHashCode(classIntrospector) : "none"); - } - - private static ClassBasedModelFactory createEnumModels(BeansWrapper wrapper) { - if (ENUMS_MODEL_CTOR != null) { - try { - return (ClassBasedModelFactory) ENUMS_MODEL_CTOR.newInstance( - wrapper); - } catch (Exception e) { - throw new UndeclaredThrowableException(e); - } - } else { - return null; - } - } - - private static Constructor enumsModelCtor() { - try { - // Check if Enums are available on this platform - Class.forName("java.lang.Enum"); - // If they are, return the appropriate constructor for enum models - return Class.forName( - "org.apache.freemarker.core.model.impl.beans._EnumModels").getDeclaredConstructor( - BeansWrapper.class); - } catch (Exception e) { - // Otherwise, return null - return null; - } - } - - /** - * Used for - * {@link MethodAppearanceFineTuner#process} - * to store the results; see there. - */ - static public final class MethodAppearanceDecision { - private PropertyDescriptor exposeAsProperty; - private String exposeMethodAs; - private boolean methodShadowsProperty; - - void setDefaults(Method m) { - exposeAsProperty = null; - exposeMethodAs = m.getName(); - methodShadowsProperty = true; - } - - public PropertyDescriptor getExposeAsProperty() { - return exposeAsProperty; - } - - public void setExposeAsProperty(PropertyDescriptor exposeAsProperty) { - this.exposeAsProperty = exposeAsProperty; - } - - public String getExposeMethodAs() { - return exposeMethodAs; - } - - public void setExposeMethodAs(String exposeMethodAs) { - this.exposeMethodAs = exposeMethodAs; - } - - public boolean getMethodShadowsProperty() { - return methodShadowsProperty; - } - - public void setMethodShadowsProperty(boolean methodShadowsProperty) { - this.methodShadowsProperty = methodShadowsProperty; - } - - } - - /** - * Used for {@link MethodAppearanceFineTuner#process} as input parameter; see there. - */ - static public final class MethodAppearanceDecisionInput { - private Method method; - private Class<?> containingClass; - - void setMethod(Method method) { - this.method = method; - } - - void setContainingClass(Class<?> containingClass) { - this.containingClass = containingClass; - } - - public Method getMethod() { - return method; - } - - public Class/*<?>*/ getContainingClass() { - return containingClass; - } - - } - -}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBuilder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBuilder.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBuilder.java deleted file mode 100644 index 2c0dcc0..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBuilder.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.Map; -import java.util.WeakHashMap; - -import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core.Version; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder; - -/** - * Gets/creates a {@link BeansWrapper} singleton instance that's already configured as specified in the properties of - * this object; this is recommended over using the {@link BeansWrapper} constructors. The returned instance can't be - * further configured (it's write protected). - * - * <p>The builder meant to be used as a drop-away object (not stored in a field), like in this example: - * <pre> - * BeansWrapper beansWrapper = new BeansWrapperBuilder(Configuration.VERSION_2_3_21).build(); - * </pre> - * - * <p>Or, a more complex example:</p> - * <pre> - * // Create the builder: - * BeansWrapperBuilder builder = new BeansWrapperBuilder(Configuration.VERSION_2_3_21); - * // Set desired BeansWrapper configuration properties: - * builder.setUseModelCache(true); - * builder.setExposeFields(true); - * - * // Get the singleton: - * BeansWrapper beansWrapper = builder.build(); - * // You don't need the builder anymore. - * </pre> - * - * <p>Despite that builders aren't meant to be used as long-lived objects (singletons), the builder is thread-safe after - * you have stopped calling its setters and it was safely published (see JSR 133) to other threads. This can be useful - * if you have to put the builder into an IoC container, rather than the singleton it produces. - * - * <p>The main benefit of using a builder instead of a {@link BeansWrapper} constructor is that this way the - * internal object wrapping-related caches (most notably the class introspection cache) will come from a global, - * JVM-level (more precisely, {@code freemarker.jar}-class-loader-level) cache. Also the {@link BeansWrapper} singletons - * themselves are stored in this global cache. Some of the wrapping-related caches are expensive to build and can take - * significant amount of memory. Using builders, components that use FreeMarker will share {@link BeansWrapper} - * instances and said caches even if they use separate FreeMarker {@link Configuration}-s. (Many Java libraries use - * FreeMarker internally, so {@link Configuration} sharing is not an option.) - * - * <p>Note that the returned {@link BeansWrapper} instances are only weak-referenced from inside the builder mechanism, - * so singletons are garbage collected when they go out of usage, just like non-singletons. - * - * <p>About the object wrapping-related caches: - * <ul> - * <li><p>Class introspection cache: Stores information about classes that once had to be wrapped. The cache is - * stored in the static fields of certain FreeMarker classes. Thus, if you have two {@link BeansWrapper} - * instances, they might share the same class introspection cache. But if you have two - * {@code freemarker.jar}-s (typically, in two Web Application's {@code WEB-INF/lib} directories), those won't - * share their caches (as they don't share the same FreeMarker classes). - * Also, currently there's a separate cache for each permutation of the property values that influence class - * introspection: {@link BeansWrapperBuilder#setExposeFields(boolean) expose_fields} and - * {@link BeansWrapperBuilder#setExposureLevel(int) exposure_level}. So only {@link BeansWrapper} where those - * properties are the same may share class introspection caches among each other. - * </li> - * <li><p>Model caches: These are local to a {@link BeansWrapper}. {@link BeansWrapperBuilder} returns the same - * {@link BeansWrapper} instance for equivalent properties (unless the existing instance was garbage collected - * and thus a new one had to be created), hence these caches will be re-used too. {@link BeansWrapper} instances - * are cached in the static fields of FreeMarker too, but there's a separate cache for each - * Thread Context Class Loader, which in a servlet container practically means a separate cache for each Web - * Application (each servlet context). (This is like so because for resolving class names to classes FreeMarker - * uses the Thread Context Class Loader, so the result of the resolution can be different for different - * Thread Context Class Loaders.) The model caches are: - * <ul> - * <li><p> - * Static model caches: These are used by the hash returned by {@link BeansWrapper#getEnumModels()} and - * {@link BeansWrapper#getStaticModels()}, for caching {@link TemplateModel}-s for the static methods/fields - * and Java enums that were accessed through them. To use said hashes, you have to put them - * explicitly into the data-model or expose them to the template explicitly otherwise, so in most applications - * these caches aren't unused. - * </li> - * <li><p> - * Instance model cache: By default off (see {@link BeansWrapper#setUseModelCache(boolean)}). Caches the - * {@link TemplateModel}-s for all Java objects that were accessed from templates. - * </li> - * </ul> - * </li> - * </ul> - * - * <p>Note that what this method documentation says about {@link BeansWrapper} also applies to - * {@link DefaultObjectWrapperBuilder}. - * - * @since 2.3.21 - */ -public class BeansWrapperBuilder extends BeansWrapperConfiguration { - - private final static Map<ClassLoader, Map<BeansWrapperConfiguration, WeakReference<BeansWrapper>>> - INSTANCE_CACHE = new WeakHashMap<>(); - private final static ReferenceQueue<BeansWrapper> INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue<>(); - - private static class BeansWrapperFactory - implements _BeansAPI._BeansWrapperSubclassFactory<BeansWrapper, BeansWrapperConfiguration> { - - private static final BeansWrapperFactory INSTANCE = new BeansWrapperFactory(); - - @Override - public BeansWrapper create(BeansWrapperConfiguration bwConf) { - return new BeansWrapper(bwConf, true); - } - - } - - /** - * See {@link BeansWrapperConfiguration#BeansWrapperConfiguration(Version)}. - */ - public BeansWrapperBuilder(Version incompatibleImprovements) { - super(incompatibleImprovements); - } - - /** For unit testing only */ - static void clearInstanceCache() { - synchronized (INSTANCE_CACHE) { - INSTANCE_CACHE.clear(); - } - } - - /** - * For unit testing only - */ - static Map<ClassLoader, Map<BeansWrapperConfiguration, WeakReference<BeansWrapper>>> getInstanceCache() { - return INSTANCE_CACHE; - } - - /** - * Returns a {@link BeansWrapper} instance that matches the settings of this builder. This will be possibly a - * singleton that is also in use elsewhere, not necessarily a new object. - */ - public BeansWrapper build() { - return _BeansAPI.getBeansWrapperSubclassSingleton( - this, INSTANCE_CACHE, INSTANCE_CACHE_REF_QUEUE, BeansWrapperFactory.INSTANCE); - } - -} \ No newline at end of file
