freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [13/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.
Date Tue, 28 Feb 2017 22:57:47 GMT
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


Mime
View raw message