FreeMarker代码分析第十三篇

本文详细分析了FreeMarker库中beans包的三个关键类:SetAdapter、SimpleMapModel和SimpleMethod。SetAdapter实现了对TemplateCollectionModel的适配,提供对Set的模板支持;SimpleMapModel为简单映射模型,用于在BeansWrapper简单模式下提供Map接口;SimpleMethod则为基础非重载方法模型,提供了方法调用的实现。通过对这些源码的解析,读者可以更深入理解FreeMarker如何处理Java对象在模板中的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2021SC@SDUSC

beans包

SetAdapter.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.util.Set;

import freemarker.template.TemplateCollectionModel;

/**
 */
class SetAdapter extends CollectionAdapter implements Set {
    SetAdapter(TemplateCollectionModel model, BeansWrapper wrapper) {
        super(model, wrapper);
    }
}

SimpleMapModel.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.util.List;
import java.util.Map;

import freemarker.core.CollectionAndSequence;
import freemarker.ext.util.ModelFactory;
import freemarker.ext.util.WrapperTemplateModel;
import freemarker.template.AdapterTemplateModel;
import freemarker.template.MapKeyValuePairIterator;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleSequence;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateHashModelEx2;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelWithAPISupport;
import freemarker.template.WrappingTemplateModel;
import freemarker.template.utility.RichObjectWrapper;

/**
 * Model used by {@link BeansWrapper} when <tt>simpleMapWrapper</tt>
 * mode is enabled. Provides a simple hash model interface to the
 * underlying map (does not copy like {@link freemarker.template.SimpleHash}),
 * and a method interface to non-string keys.
 */
public class SimpleMapModel extends WrappingTemplateModel 
implements TemplateHashModelEx2, TemplateMethodModelEx, AdapterTemplateModel, 
WrapperTemplateModel, TemplateModelWithAPISupport {
    static final ModelFactory FACTORY =
        new ModelFactory()
        {
            @Override
            public TemplateModel create(Object object, ObjectWrapper wrapper) {
                return new SimpleMapModel((Map) object, (BeansWrapper) wrapper);
            }
        };

    private final Map map;
    
    public SimpleMapModel(Map map, BeansWrapper wrapper) {
        super(wrapper);
        this.map = map;
    }

    @Override
    public TemplateModel get(String key) throws TemplateModelException {
        Object val = map.get(key);
        if (val == null) {
            if (key.length() == 1) {
                // just check for Character key if this is a single-character string
                Character charKey = Character.valueOf(key.charAt(0));
                val = map.get(charKey);
                if (val == null && !(map.containsKey(key) || map.containsKey(charKey))) {
                    return null;
                }
            } else if (!map.containsKey(key)) {
                return null;
            }
        }
        return wrap(val);
    }
    
    @Override
    public Object exec(List args) throws TemplateModelException {
        Object key = ((BeansWrapper) getObjectWrapper()).unwrap((TemplateModel) args.get(0));
        Object value = map.get(key);
        if (value == null && !map.containsKey(key)) {
            return null;
        }
        return wrap(value);
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public TemplateCollectionModel keys() {
        return new CollectionAndSequence(new SimpleSequence(map.keySet(), getObjectWrapper()));
    }

    @Override
    public TemplateCollectionModel values() {
        return new CollectionAndSequence(new SimpleSequence(map.values(), getObjectWrapper()));
    }
    
    @Override
    public KeyValuePairIterator keyValuePairIterator() {
        return new MapKeyValuePairIterator(map, getObjectWrapper());
    }

    @Override
    public Object getAdaptedObject(Class hint) {
        return map;
    }
    
    @Override
    public Object getWrappedObject() {
        return map;
    }

    @Override
    public TemplateModel getAPI() throws TemplateModelException {
        return ((RichObjectWrapper) getObjectWrapper()).wrapAsAPI(map);
    }
}

SimpleMethod.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.lang.reflect.Array;
import java.lang.reflect.Member;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import freemarker.core.TemplateMarkupOutputModel;
import freemarker.core._DelayedFTLTypeDescription;
import freemarker.core._DelayedOrdinal;
import freemarker.core._ErrorDescriptionBuilder;
import freemarker.core._TemplateModelException;
import freemarker.template.ObjectWrapperAndUnwrapper;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.ClassUtil;

/**
 * This class is used for as a base for non-overloaded method models and for constructors.
 * (For overloaded methods and constructors see {@link OverloadedMethods}.)
 */
class SimpleMethod {
    
    static final String MARKUP_OUTPUT_TO_STRING_TIP
            = "A markup output value can be converted to markup string like value?markup_string. "
              + "But consider if the Java method whose argument it will be can handle markup strings properly.";
    
    private final Member member;
    private final Class[] argTypes;
    
    protected SimpleMethod(Member member, Class[] argTypes) {
        this.member = member;
        this.argTypes = argTypes;
    }
    
    Object[] unwrapArguments(List arguments, BeansWrapper wrapper) throws TemplateModelException {
        if (arguments == null) {
            arguments = Collections.EMPTY_LIST;
        }
        boolean isVarArg = _MethodUtil.isVarargs(member);
        int typesLen = argTypes.length;
        if (isVarArg) {
            if (typesLen - 1 > arguments.size()) {
                throw new _TemplateModelException(
                        _MethodUtil.invocationErrorMessageStart(member),
                        " takes at least ", Integer.valueOf(typesLen - 1),
                        typesLen - 1 == 1 ? " argument" : " arguments", ", but ",
                        Integer.valueOf(arguments.size()), " was given.");
            }
        } else if (typesLen != arguments.size()) {
            throw new _TemplateModelException(
                    _MethodUtil.invocationErrorMessageStart(member), 
                    " takes ", Integer.valueOf(typesLen), typesLen == 1 ? " argument" : " arguments", ", but ",
                    Integer.valueOf(arguments.size()), " was given.");
        }
         
        Object[] args = unwrapArguments(arguments, argTypes, isVarArg, wrapper);
        return args;
    }

    private Object[] unwrapArguments(List args, Class[] argTypes, boolean isVarargs,
            BeansWrapper w) 
    throws TemplateModelException {
        if (args == null) return null;
        
        int typesLen = argTypes.length;
        int argsLen = args.size();
        
        Object[] unwrappedArgs = new Object[typesLen];
        
        // Unwrap arguments:
        Iterator it = args.iterator();
        int normalArgCnt = isVarargs ? typesLen - 1 : typesLen; 
        int argIdx = 0;
        while (argIdx < normalArgCnt) {
            Class argType = argTypes[argIdx];
            TemplateModel argVal = (TemplateModel) it.next();
            Object unwrappedArgVal = w.tryUnwrapTo(argVal, argType);
            if (unwrappedArgVal == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
                throw createArgumentTypeMismarchException(argIdx, argVal, argType);
            }
            if (unwrappedArgVal == null && argType.isPrimitive()) {
                throw createNullToPrimitiveArgumentException(argIdx, argType); 
            }
            
            unwrappedArgs[argIdx++] = unwrappedArgVal;
        }
        if (isVarargs) {
            // The last argType, which is the vararg type, wasn't processed yet.
            
            Class varargType = argTypes[typesLen - 1];
            Class varargItemType = varargType.getComponentType();
            if (!it.hasNext()) {
                unwrappedArgs[argIdx++] = Array.newInstance(varargItemType, 0);
            } else {
                TemplateModel argVal = (TemplateModel) it.next();
                
                Object unwrappedArgVal;
                // We first try to treat the last argument as a vararg *array*.
                // This is consistent to what OverloadedVarArgMethod does.
                if (argsLen - argIdx == 1
                        && (unwrappedArgVal = w.tryUnwrapTo(argVal, varargType))
                            != ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
                    // It was a vararg array.
                    unwrappedArgs[argIdx++] = unwrappedArgVal;
                } else {
                    // It wasn't a vararg array, so we assume it's a vararg
                    // array *item*, possibly followed by further ones.
                    int varargArrayLen = argsLen - argIdx;
                    Object varargArray = Array.newInstance(varargItemType, varargArrayLen);
                    for (int varargIdx = 0; varargIdx < varargArrayLen; varargIdx++) {
                        TemplateModel varargVal = (TemplateModel) (varargIdx == 0 ? argVal : it.next());
                        Object unwrappedVarargVal = w.tryUnwrapTo(varargVal, varargItemType);
                        if (unwrappedVarargVal == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
                            throw createArgumentTypeMismarchException(
                                    argIdx + varargIdx, varargVal, varargItemType);
                        }
                        
                        if (unwrappedVarargVal == null && varargItemType.isPrimitive()) {
                            throw createNullToPrimitiveArgumentException(argIdx + varargIdx, varargItemType); 
                        }
                        Array.set(varargArray, varargIdx, unwrappedVarargVal);
                    }
                    unwrappedArgs[argIdx++] = varargArray;
                }
            }
        }
        
        return unwrappedArgs;
    }

    private TemplateModelException createArgumentTypeMismarchException(
            int argIdx, TemplateModel argVal, Class targetType) {
        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
                _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: Can't convert the ",
                new _DelayedOrdinal(Integer.valueOf(argIdx + 1)),
                " argument's value to the target Java type, ", ClassUtil.getShortClassName(targetType),
                ". The type of the actual value was: ", new _DelayedFTLTypeDescription(argVal));
        if (argVal instanceof TemplateMarkupOutputModel && (targetType.isAssignableFrom(String.class))) {
            desc.tip(MARKUP_OUTPUT_TO_STRING_TIP);
        }
        return new _TemplateModelException(desc);
    }

    private TemplateModelException createNullToPrimitiveArgumentException(int argIdx, Class targetType) {
        return new _TemplateModelException(
                _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: The value of the ",
                new _DelayedOrdinal(Integer.valueOf(argIdx + 1)),
                " argument was null, but the target Java parameter type (", ClassUtil.getShortClassName(targetType),
                ") is primitive and so can't store null.");
    }
    
    protected Member getMember() {
        return member;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值