FreeMarker代码分析第十一篇

2021SC@SDUSC

FreeMarker代码分析第十一篇

beans包

BeansWrapper.java

代码分析

/*
 * 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 freemarker.ext.beans;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
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 freemarker.core.BugException;
import freemarker.core._DelayedFTLTypeDescription;
import freemarker.core._DelayedShortClassName;
import freemarker.core._TemplateModelException;
import freemarker.ext.util.ModelCache;
import freemarker.ext.util.ModelFactory;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.log.Logger;
import freemarker.template.AdapterTemplateModel;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.ObjectWrapper;
import freemarker.template.ObjectWrapperAndUnwrapper;
import freemarker.template.SimpleObjectWrapper;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelAdapter;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.Version;
import freemarker.template._TemplateAPI;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.RichObjectWrapper;
import freemarker.template.utility.WriteProtectable;

/**
 * {@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 = Logger.getLogger("freemarker.beans");

    /**
     * @deprecated Use {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} instead. It's not a public field
     *             anyway.
     */
    @Deprecated
    static final Object CAN_NOT_UNWRAP = ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
    
    /**
     * At this level of exposure, all methods and properties of the
     * wrapped objects are exposed to the template, and the {@link MemberAccessPolicy}
     * will be ignored.
     */
    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 TemplateModel nullModel = null;
    private int defaultDateType; // initialized from the BeansWrapperConfiguration
    private ObjectWrapper outerIdentity = this;
    private boolean methodsShadowItems = true;
    private boolean simpleMapWrapper;  // initialized from the BeansWrapperConfiguration
    private boolean strict;  // initialized from the BeansWrapperConfiguration
    private boolean preferIndexedReadMethod; // initialized from the BeansWrapperConfiguration
    
    private final Version incompatibleImprovements;
    
    /**
     * Creates a new instance with the incompatible-improvements-version specified in
     * {@link Configuration#DEFAULT_INCOMPATIBLE_IMPROVEMENTS}.
     * 
     * @deprecated Use {@link BeansWrapperBuilder} or, in rare cases, {@link #BeansWrapper(Version)} instead.
     */
    @Deprecated
    public BeansWrapper() {
   
        this(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        // Attention! Don't change fields here, as the instance is possibly already visible to other threads.  
    }
    
    /**
     * 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 fixed 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 fixed, 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>  
     *     <li>
     *       <p>2.3.26 (or higher):
     *       The default of {@link BeansWrapper#getTreatDefaultMethodsAsBeanMembers()} changes from {@code false} to
     *       {@code true}. Thus, Java 8 default methods (and the bean properties they define) are exposed, despite that
     *       {@link java.beans.Introspector} (the official JavaBeans introspector) ignores them, at least as of Java 8. 
     *     </li>  
     *     <li>
     *       <p>2.3.27 (or higher):
     *       The default of the {@link #setPreferIndexedReadMethod(boolean) preferIndexedReadMethod} setting changes
     *       from {@code true} to {@code false}.
     *     </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.
    }
    
    private static volatile boolean ftmaDeprecationWarnLogged;
    
    /**
     * 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) {
   
        // Backward-compatibility hack for "finetuneMethodAppearance" overrides to work:
        if (bwConf.getMethodAppearanceFineTuner() == null) {
   
            Class<?> thisClass = this.getClass();
            boolean overridden = false;
            boolean testFailed = false;
            try {
   
                while (!overridden
                        && thisClass != DefaultObjectWrapper.class
                        && thisClass != BeansWrapper.class
                        && thisClass != SimpleObjectWrapper.class) {
   
                    try {
   
                        thisClass.getDeclaredMethod("finetuneMethodAppearance",
                                new Class<?>[] {
    Class.class, Method.class, MethodAppearanceDecision.class });
                        overridden = true;
                    } catch (NoSuchMethodException e) {
   
                        thisClass = thisClass.getSuperclass();
                    }
                }
            } catch (Throwable e) {
   
                // The security manager sometimes doesn't allow this
                LOG.info("Failed to check if finetuneMethodAppearance is overidden in " + thisClass.getName()
                        + "; acting like if it was, but this way it won't utilize the shared class introspection "
                        + "cache.",
                        e);
                overridden = true;
                testFailed = true;
            }
            if (overridden) {
   
                if (!testFailed && !ftmaDeprecationWarnLogged) {
   
                    LOG.warn("Overriding " + BeansWrapper.class.getName() + ".finetuneMethodAppearance is deprecated "
                            + "and will be banned sometimes in the future. Use setMethodAppearanceFineTuner instead.");
                    ftmaDeprecationWarnLogged = true;
                }
                bwConf = (BeansWrapperConfiguration) bwConf.clone(false);
                bwConf.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
   

                    @Override
                    public void process(
                            MethodAppearanceDecisionInput in, MethodAppearanceDecision out) {
   
                        BeansWrapper.this.finetuneMethodAppearance(in.getContainingClass(), in.getMethod(), out);
                    }
                    
                });
            }
        }
        
        this.incompatibleImprovements = bwConf.getIncompatibleImprovements();  // normalized
        
        simpleMapWrapper = bwConf.isSimpleMapWrapper();
        preferIndexedReadMethod =  bwConf.getPreferIndexedReadMethod();
        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(
                    _BeansAPI.getClassIntrospectorBuilder(bwConf), sharedIntrospectionLock, false, false);
        } 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 = _BeansAPI.getClassIntrospectorBuilder(bwConf).build();
            sharedIntrospectionLock = classIntrospector.getSharedLock(); 
        }
        
        falseModel = new BooleanModel(Boolean.FALSE, this);
        trueModel = new BooleanModel(Boolean.TRUE, this);
        
        staticModels = new StaticModels(this);
        enumModels = new _EnumModels(this);
        modelCache = new BeansModelCache(this);
        setUseCache(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 " + this.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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值