lucee.transformer.bytecode.statement.udf.Function.java Source code

Java tutorial

Introduction

Here is the source code for lucee.transformer.bytecode.statement.udf.Function.java

Source

/**
 *
 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/
package lucee.transformer.bytecode.statement.udf;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lucee.commons.lang.CFTypes;
import lucee.commons.lang.StringUtil;
import lucee.runtime.Component;
import lucee.runtime.exp.TemplateException;
import lucee.runtime.listener.AppListenerUtil;
import lucee.runtime.type.FunctionArgument;
import lucee.runtime.type.FunctionArgumentImpl;
import lucee.runtime.type.FunctionArgumentLight;
import lucee.runtime.type.util.ComponentUtil;
import lucee.transformer.bytecode.Body;
import lucee.transformer.bytecode.BytecodeContext;
import lucee.transformer.bytecode.BytecodeException;
import lucee.transformer.bytecode.Literal;
import lucee.transformer.bytecode.Page;
import lucee.transformer.bytecode.Position;
import lucee.transformer.bytecode.cast.CastBoolean;
import lucee.transformer.bytecode.cast.CastInt;
import lucee.transformer.bytecode.cast.CastString;
import lucee.transformer.bytecode.expression.ExprBoolean;
import lucee.transformer.bytecode.expression.ExprInt;
import lucee.transformer.bytecode.expression.ExprString;
import lucee.transformer.bytecode.expression.Expression;
import lucee.transformer.bytecode.expression.var.Variable;
import lucee.transformer.bytecode.literal.LitBoolean;
import lucee.transformer.bytecode.literal.LitInteger;
import lucee.transformer.bytecode.literal.LitString;
import lucee.transformer.bytecode.statement.Argument;
import lucee.transformer.bytecode.statement.HasBody;
import lucee.transformer.bytecode.statement.IFunction;
import lucee.transformer.bytecode.statement.StatementBaseNoFinal;
import lucee.transformer.bytecode.statement.tag.Attribute;
import lucee.transformer.bytecode.util.ASMConstants;
import lucee.transformer.bytecode.util.ASMUtil;
import lucee.transformer.bytecode.util.ExpressionUtil;
import lucee.transformer.bytecode.util.Types;
import lucee.transformer.cfml.evaluator.EvaluatorException;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

public abstract class Function extends StatementBaseNoFinal implements Opcodes, IFunction, HasBody {

    // Scope variablesScope()
    static final Method VARIABLE_SCOPE = new Method("variablesScope", Types.VARIABLES, new Type[] {});
    // Scope variablesScope()
    static final Method GET_PAGESOURCE = new Method("getPageSource", Types.PAGE_SOURCE, new Type[] {});

    // Object set(String,Object)
    static final Method SET_STR = new Method("set", Types.OBJECT, new Type[] { Types.STRING, Types.OBJECT });

    static final Method SET_KEY = new Method("set", Types.OBJECT,
            new Type[] { Types.COLLECTION_KEY, Types.OBJECT });

    /*static final Method REG_UDF_STR = new Method(
     "registerUDF",
     Types.VOID,
     new Type[]{Types.STRING,Types.UDF_PROPERTIES}
      );*/

    static final Method REG_UDF_KEY = new Method("registerUDF", Types.VOID,
            new Type[] { Types.COLLECTION_KEY, Types.UDF_PROPERTIES });

    private static final ExprString ANY = LitString.toExprString("any");

    // <init>(Page,FunctionArgument[],int String,String,boolean);
    private static final Type FUNCTION_ARGUMENT = Type.getType(FunctionArgument.class);
    private static final Type FUNCTION_ARGUMENT_IMPL = Type.getType(FunctionArgumentImpl.class);
    private static final Type FUNCTION_ARGUMENT_LIGHT = Type.getType(FunctionArgumentLight.class);
    private static final Type FUNCTION_ARGUMENT_ARRAY = Type.getType(FunctionArgument[].class);

    protected static final Method INIT_UDF_IMPL_PROP = new Method("<init>", Types.VOID,
            new Type[] { Types.UDF_PROPERTIES });

    private static final Method INIT_UDF_PROPERTIES_STRTYPE = new Method("<init>", Types.VOID,
            new Type[] { Types.PAGE_SOURCE, FUNCTION_ARGUMENT_ARRAY, Types.INT_VALUE, Types.STRING, Types.STRING,
                    Types.STRING, Types.BOOLEAN_VALUE, Types.INT_VALUE, Types.BOOLEAN, Types.STRING, Types.STRING,
                    Types.STRING, Types.BOOLEAN, Types.BOOLEAN, Types.OBJECT, Types.INTEGER, Page.STRUCT_IMPL });
    private static final Method INIT_UDF_PROPERTIES_SHORTTYPE = new Method("<init>", Types.VOID,
            new Type[] { Types.PAGE_SOURCE, FUNCTION_ARGUMENT_ARRAY, Types.INT_VALUE, Types.STRING,
                    Types.SHORT_VALUE, Types.STRING, Types.BOOLEAN_VALUE, Types.INT_VALUE, Types.BOOLEAN,
                    Types.STRING, Types.STRING, Types.STRING, Types.BOOLEAN, Types.BOOLEAN, Types.OBJECT,
                    Types.INTEGER, Page.STRUCT_IMPL });
    private static final Method INIT_UDF_PROPERTIES_SHORTTYPE_LIGHT = new Method("<init>", Types.VOID,
            new Type[] { Types.PAGE_SOURCE, FUNCTION_ARGUMENT_ARRAY, Types.INT_VALUE, Types.STRING,
                    Types.SHORT_VALUE, Types.STRING, Types.BOOLEAN_VALUE, Types.INT_VALUE });

    // FunctionArgumentImpl(String name,String type,boolean required,int defaultType,String dspName,String hint,StructImpl meta)
    private static final Method INIT_FAI_KEY1 = new Method("<init>", Types.VOID,
            new Type[] { Types.COLLECTION_KEY });
    private static final Method INIT_FAI_KEY3 = new Method("<init>", Types.VOID,
            new Type[] { Types.COLLECTION_KEY, Types.STRING, Types.SHORT_VALUE });
    private static final Method INIT_FAI_KEY4 = new Method("<init>", Types.VOID,
            new Type[] { Types.COLLECTION_KEY, Types.STRING, Types.SHORT_VALUE, Types.BOOLEAN_VALUE });
    private static final Method INIT_FAI_KEY5 = new Method("<init>", Types.VOID, new Type[] { Types.COLLECTION_KEY,
            Types.STRING, Types.SHORT_VALUE, Types.BOOLEAN_VALUE, Types.INT_VALUE });
    private static final Method INIT_FAI_KEY6 = new Method("<init>", Types.VOID, new Type[] { Types.COLLECTION_KEY,
            Types.STRING, Types.SHORT_VALUE, Types.BOOLEAN_VALUE, Types.INT_VALUE, Types.BOOLEAN_VALUE });
    private static final Method INIT_FAI_KEY7 = new Method("<init>", Types.VOID,
            new Type[] { Types.COLLECTION_KEY, Types.STRING, Types.SHORT_VALUE, Types.BOOLEAN_VALUE,
                    Types.INT_VALUE, Types.BOOLEAN_VALUE, Types.STRING });
    private static final Method INIT_FAI_KEY8 = new Method("<init>", Types.VOID,
            new Type[] { Types.COLLECTION_KEY, Types.STRING, Types.SHORT_VALUE, Types.BOOLEAN_VALUE,
                    Types.INT_VALUE, Types.BOOLEAN_VALUE, Types.STRING, Types.STRING });
    private static final Method INIT_FAI_KEY9 = new Method("<init>", Types.VOID,
            new Type[] { Types.COLLECTION_KEY, Types.STRING, Types.SHORT_VALUE, Types.BOOLEAN_VALUE,
                    Types.INT_VALUE, Types.BOOLEAN_VALUE, Types.STRING, Types.STRING, Page.STRUCT_IMPL });
    private static final Method[] INIT_FAI_KEY = new Method[] { INIT_FAI_KEY1, INIT_FAI_KEY3, INIT_FAI_KEY4,
            INIT_FAI_KEY5, INIT_FAI_KEY6, INIT_FAI_KEY7, INIT_FAI_KEY8, INIT_FAI_KEY9 };
    private static final Method[] INIT_FAI_KEY_LIGHT = new Method[] { INIT_FAI_KEY1, INIT_FAI_KEY3 };

    ExprString name;
    ExprString returnType = ANY;
    ExprBoolean output = LitBoolean.TRUE;
    ExprBoolean bufferOutput;
    //ExprBoolean abstry=LitBoolean.FALSE;
    int access = Component.ACCESS_PUBLIC;
    ExprString displayName = LitString.EMPTY;
    ExprString hint = LitString.EMPTY;
    Body body;
    List<Argument> arguments = new ArrayList<Argument>();
    Map<String, Attribute> metadata;
    ExprString returnFormat;
    ExprString description;
    ExprBoolean secureJson;
    ExprBoolean verifyClient;
    ExprInt localMode;
    protected int valueIndex;
    protected int arrayIndex;
    private Literal cachedWithin;
    private boolean _abstract;
    private boolean _final;

    public Function(Page page, String name, int access, String returnType, Body body, Position start,
            Position end) {
        super(start, end);
        this.name = LitString.toExprString(name);
        this.access = access;
        if (!StringUtil.isEmpty(returnType))
            this.returnType = LitString.toExprString(returnType);
        this.body = body;
        body.setParent(this);
        int[] indexes = page.addFunction(this);
        valueIndex = indexes[VALUE_INDEX];
        arrayIndex = indexes[ARRAY_INDEX];
    }

    public Function(Page page, Expression name, Expression returnType, Expression returnFormat, Expression output,
            Expression bufferOutput, int access, Expression displayName, Expression description, Expression hint,
            Expression secureJson, Expression verifyClient, Expression localMode, Literal cachedWithin,
            boolean _abstract, boolean _final, Body body, Position start, Position end) {
        super(start, end);

        this.name = CastString.toExprString(name);
        this.returnType = CastString.toExprString(returnType);
        this.returnFormat = returnFormat != null ? CastString.toExprString(returnFormat) : null;
        this.output = CastBoolean.toExprBoolean(output);
        this.bufferOutput = bufferOutput == null ? null : CastBoolean.toExprBoolean(bufferOutput);
        this.access = access;
        this.description = description != null ? CastString.toExprString(description) : null;
        this.displayName = CastString.toExprString(displayName);
        this.hint = CastString.toExprString(hint);
        this.secureJson = secureJson != null ? CastBoolean.toExprBoolean(secureJson) : null;
        this.verifyClient = verifyClient != null ? CastBoolean.toExprBoolean(verifyClient) : null;
        this.cachedWithin = cachedWithin;
        this._abstract = _abstract;
        this._final = _final;
        this.localMode = toLocalMode(localMode, null);

        this.body = body;
        body.setParent(this);
        int[] indexes = page.addFunction(this);
        valueIndex = indexes[VALUE_INDEX];
        arrayIndex = indexes[ARRAY_INDEX];

    }

    public static ExprInt toLocalMode(Expression expr, ExprInt defaultValue) {
        int mode = -1;
        if (expr instanceof Literal) {
            String str = ((Literal) expr).getString();
            str = str.trim().toLowerCase();
            mode = AppListenerUtil.toLocalMode(str, -1);
        }
        if (mode == -1)
            return defaultValue;
        return LitInteger.toExpr(mode);
    }

    /*private static void checkNameConflict(ExprString expr) {
       if(expr instanceof LitString){
     String name=((LitString)expr).getString();
     if()
       }
    }*/

    /**
     * @see lucee.transformer.bytecode.statement.IFunction#writeOut(lucee.transformer.bytecode.BytecodeContext, int)
     */
    public final void writeOut(BytecodeContext bc, int type) throws BytecodeException {
        ExpressionUtil.visitLine(bc, getStart());
        _writeOut(bc, type);
        ExpressionUtil.visitLine(bc, getEnd());
    }

    /**
     * @see lucee.transformer.bytecode.statement.StatementBase#_writeOut(lucee.transformer.bytecode.BytecodeContext)
     */
    public final void _writeOut(BytecodeContext bc) throws BytecodeException {
        _writeOut(bc, PAGE_TYPE_REGULAR);
    }

    public abstract void _writeOut(BytecodeContext bc, int pageType) throws BytecodeException;

    public final void loadUDFProperties(BytecodeContext bc, int valueIndex, int arrayIndex, boolean closure)
            throws BytecodeException {
        BytecodeContext constr = bc.getConstructor();
        GeneratorAdapter cga = constr.getAdapter();
        GeneratorAdapter ga = bc.getAdapter();

        // store
        cga.visitVarInsn(ALOAD, 0);
        cga.visitFieldInsn(GETFIELD, bc.getClassName(), "udfs", Types.UDF_PROPERTIES_ARRAY.toString());
        cga.push(arrayIndex);
        createUDFProperties(constr, valueIndex, closure);
        //cga.visitInsn(DUP_X2);
        cga.visitInsn(AASTORE);

        // get
        ga.visitVarInsn(ALOAD, 0);
        ga.visitFieldInsn(GETFIELD, bc.getClassName(), "udfs", Types.UDF_PROPERTIES_ARRAY.toString());
        ga.push(arrayIndex);
        ga.visitInsn(AALOAD);
    }

    public final void createUDFProperties(BytecodeContext bc, int index, boolean closure) throws BytecodeException {
        GeneratorAdapter adapter = bc.getAdapter();
        adapter.newInstance(Types.UDF_PROPERTIES_IMPL);
        adapter.dup();
        if (closure) {
            adapter.loadThis();
            adapter.invokeVirtual(Types.PAGE, GET_PAGESOURCE);
        } else
            adapter.visitVarInsn(ALOAD, 1);
        // page
        //adapter.loadLocal(0);
        //adapter.loadThis();

        // arguments
        createArguments(bc);
        // index
        adapter.push(index);
        // name
        ExpressionUtil.writeOutSilent(name, bc, Expression.MODE_REF);
        // return type
        short type = ExpressionUtil.toShortType(returnType, false, CFTypes.TYPE_UNKNOW);
        if (type == CFTypes.TYPE_UNKNOW)
            ExpressionUtil.writeOutSilent(returnType, bc, Expression.MODE_REF);
        else
            adapter.push(type);

        // return format
        if (returnFormat != null)
            ExpressionUtil.writeOutSilent(returnFormat, bc, Expression.MODE_REF);
        else
            ASMConstants.NULL(adapter);

        // output
        ExpressionUtil.writeOutSilent(output, bc, Expression.MODE_VALUE);

        // access
        writeOutAccess(bc, access);

        boolean light = type != -1;
        if (light && !LitString.EMPTY.equals(displayName))
            light = false;
        if (light && description != null && !LitString.EMPTY.equals(description))
            light = false;
        if (light && !LitString.EMPTY.equals(hint))
            light = false;
        if (light && secureJson != null)
            light = false;
        if (light && verifyClient != null)
            light = false;
        if (light && cachedWithin != null)
            light = false;
        if (light && bufferOutput != null)
            light = false;
        if (light && localMode != null)
            light = false;
        if (light && Page.hasMetaDataStruct(metadata, null))
            light = false;
        if (light) {
            adapter.invokeConstructor(Types.UDF_PROPERTIES_IMPL, INIT_UDF_PROPERTIES_SHORTTYPE_LIGHT);
            return;
        }

        // buffer output
        if (bufferOutput != null)
            ExpressionUtil.writeOutSilent(bufferOutput, bc, Expression.MODE_REF);
        else
            ASMConstants.NULL(adapter);

        // displayName
        ExpressionUtil.writeOutSilent(displayName, bc, Expression.MODE_REF);// displayName;

        // description
        if (description != null)
            ExpressionUtil.writeOutSilent(description, bc, Expression.MODE_REF);// displayName;
        else
            adapter.push("");

        // hint
        ExpressionUtil.writeOutSilent(hint, bc, Expression.MODE_REF);// hint;

        // secureJson
        if (secureJson != null)
            ExpressionUtil.writeOutSilent(secureJson, bc, Expression.MODE_REF);
        else
            ASMConstants.NULL(adapter);

        // verify client
        if (verifyClient != null)
            ExpressionUtil.writeOutSilent(verifyClient, bc, Expression.MODE_REF);
        else
            ASMConstants.NULL(adapter);

        // cachedwithin
        if (cachedWithin != null) {
            cachedWithin.writeOut(bc, Expression.MODE_REF);
        } else
            ASMConstants.NULL(adapter);
        //adapter.push(cachedWithin<0?0:cachedWithin);

        // localMode
        if (localMode != null)
            ExpressionUtil.writeOutSilent(localMode, bc, Expression.MODE_REF);
        else
            ASMConstants.NULL(adapter);

        // meta
        Page.createMetaDataStruct(bc, metadata, null);

        adapter.invokeConstructor(Types.UDF_PROPERTIES_IMPL,
                type == -1 ? INIT_UDF_PROPERTIES_STRTYPE : INIT_UDF_PROPERTIES_SHORTTYPE);

    }

    /*public final void loadUDF(BytecodeContext bc, int index) throws BytecodeException {
       // new UDF(...)
       GeneratorAdapter adapter=bc.getAdapter();
       adapter.newInstance(Types.UDF_IMPL);
       adapter.dup();
           
       loadUDFProperties(bc, index,false);
           
       adapter.invokeConstructor(Types.UDF_IMPL, INIT_UDF_IMPL_PROP);
    }*/

    public final void createUDF(BytecodeContext bc, int index, boolean closure) throws BytecodeException {
        // new UDF(...)
        GeneratorAdapter adapter = bc.getAdapter();
        adapter.newInstance(closure ? Types.CLOSURE : Types.UDF_IMPL);
        adapter.dup();

        createUDFProperties(bc, index, closure);
        //loadUDFProperties(bc, index,closure);

        adapter.invokeConstructor(closure ? Types.CLOSURE : Types.UDF_IMPL, INIT_UDF_IMPL_PROP);
    }

    private final void createArguments(BytecodeContext bc) throws BytecodeException {
        GeneratorAdapter ga = bc.getAdapter();
        ga.push(arguments.size());
        ga.newArray(FUNCTION_ARGUMENT);
        Argument arg;
        for (int i = 0; i < arguments.size(); i++) {
            arg = arguments.get(i);

            boolean canHaveKey = Variable.canRegisterKey(arg.getName());

            // CHECK if default values
            // type
            ExprString _strType = arg.getType();
            short _type = CFTypes.TYPE_UNKNOW;
            if (_strType instanceof LitString) {
                _type = CFTypes.toShortStrict(((LitString) _strType).getString(), CFTypes.TYPE_UNKNOW);
            }
            boolean useType = !canHaveKey || _type != CFTypes.TYPE_ANY;
            //boolean useStrType=useType && (_type==CFTypes.TYPE_UNDEFINED || _type==CFTypes.TYPE_UNKNOW || CFTypes.toString(_type, null)==null);

            // required
            ExprBoolean _req = arg.getRequired();
            boolean useReq = !canHaveKey || toBoolean(_req, null) != Boolean.FALSE;

            // default-type
            Expression _def = arg.getDefaultValueType();
            boolean useDef = !canHaveKey || toInt(_def, -1) != FunctionArgument.DEFAULT_TYPE_NULL;

            // pass by reference
            ExprBoolean _pass = arg.isPassByReference();
            boolean usePass = !canHaveKey || toBoolean(_pass, null) != Boolean.TRUE;

            // display-hint
            ExprString _dsp = arg.getDisplayName();
            boolean useDsp = !canHaveKey || !isLiteralEmptyString(_dsp);

            // hint
            ExprString _hint = arg.getHint();
            boolean useHint = !canHaveKey || !isLiteralEmptyString(_hint);

            // meta
            Map _meta = arg.getMetaData();
            boolean useMeta = !canHaveKey || (_meta != null && !_meta.isEmpty());
            int functionIndex = 7;
            if (!useMeta) {
                functionIndex--;
                if (!useHint) {
                    functionIndex--;
                    if (!useDsp) {
                        functionIndex--;
                        if (!usePass) {
                            functionIndex--;
                            if (!useDef) {
                                functionIndex--;
                                if (!useReq) {
                                    functionIndex--;
                                    if (!useType) {
                                        functionIndex--;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            // write out arguments   
            ga.dup();
            ga.push(i);

            // new FunctionArgument(...)
            ga.newInstance(canHaveKey && functionIndex < INIT_FAI_KEY_LIGHT.length ? FUNCTION_ARGUMENT_LIGHT
                    : FUNCTION_ARGUMENT_IMPL);
            ga.dup();
            Variable.registerKey(bc, arg.getName(), false);

            // type
            if (functionIndex >= INIT_FAI_KEY.length - 7) {
                _strType.writeOut(bc, Expression.MODE_REF);
                bc.getAdapter().push(_type);
            }
            // required
            if (functionIndex >= INIT_FAI_KEY.length - 6)
                _req.writeOut(bc, Expression.MODE_VALUE);
            // default value
            if (functionIndex >= INIT_FAI_KEY.length - 5)
                _def.writeOut(bc, Expression.MODE_VALUE);
            // pass by reference
            if (functionIndex >= INIT_FAI_KEY.length - 4)
                _pass.writeOut(bc, Expression.MODE_VALUE);
            // display-name
            if (functionIndex >= INIT_FAI_KEY.length - 3)
                _dsp.writeOut(bc, Expression.MODE_REF);
            // hint
            if (functionIndex >= INIT_FAI_KEY.length - 2)
                _hint.writeOut(bc, Expression.MODE_REF);
            //meta
            if (functionIndex == INIT_FAI_KEY.length - 1)
                Page.createMetaDataStruct(bc, _meta, null);

            if (functionIndex < INIT_FAI_KEY_LIGHT.length)
                ga.invokeConstructor(FUNCTION_ARGUMENT_LIGHT, INIT_FAI_KEY[functionIndex]);
            else
                ga.invokeConstructor(FUNCTION_ARGUMENT_IMPL, INIT_FAI_KEY[functionIndex]);

            ga.visitInsn(Opcodes.AASTORE);
        }
    }

    private final int toInt(Expression expr, int defaultValue) {
        if (expr instanceof LitInteger) {
            return ((LitInteger) expr).getInteger().intValue();
        }
        return defaultValue;
    }

    private final Boolean toBoolean(ExprBoolean expr, Boolean defaultValue) {
        if (expr instanceof LitBoolean) {
            return ((LitBoolean) expr).getBooleanValue() ? Boolean.TRUE : Boolean.FALSE;
        }
        return defaultValue;
    }

    private final boolean isLiteralEmptyString(ExprString expr) {
        if (expr instanceof LitString) {
            return StringUtil.isEmpty(((LitString) expr).getString());
        }
        return false;
    }

    private final void writeOutAccess(BytecodeContext bc, ExprString expr) {

        // write short type
        if (expr instanceof LitString) {
            int access = ComponentUtil.toIntAccess(((LitString) expr).getString(), Component.ACCESS_PUBLIC);
            bc.getAdapter().push(access);
        } else
            bc.getAdapter().push(Component.ACCESS_PUBLIC);
    }

    private final void writeOutAccess(BytecodeContext bc, int access) {
        bc.getAdapter().push(access);
    }

    public final void addArgument(String name, String type, boolean required, Expression defaultValue) {
        addArgument(LitString.toExprString(name), LitString.toExprString(type), LitBoolean.toExprBoolean(required),
                defaultValue, LitBoolean.TRUE, LitString.EMPTY, LitString.EMPTY, null);
    }

    public final void addArgument(Expression name, Expression type, Expression required, Expression defaultValue,
            ExprBoolean passByReference, Expression displayName, Expression hint, Map meta) {
        arguments.add(new Argument(name, type, required, defaultValue, passByReference, displayName, hint, meta));
    }

    /**
     * @return the arguments
     */
    public final List<Argument> getArguments() {
        return arguments;
    }

    /**
     * @return the body
     */
    public final Body getBody() {
        return body;
    }

    public final void setMetaData(Map<String, Attribute> metadata) {
        this.metadata = metadata;
    }

    public final void setHint(String hint) {
        this.hint = LitString.toExprString(hint);
    }

    public final void addAttribute(Attribute attr) throws TemplateException {
        String name = attr.getName().toLowerCase();
        // name
        if ("name".equals(name)) {
            throw new BytecodeException("name cannot be defined twice", getStart());
            //this.name=CastString.toExprString(attr.getValue());
        } else if ("returntype".equals(name)) {
            this.returnType = toLitString(name, attr.getValue());
        } else if ("access".equals(name)) {

            LitString ls = toLitString(name, attr.getValue());
            String strAccess = ls.getString();
            int acc = ComponentUtil.toIntAccess(strAccess, -1);
            if (acc == -1)
                throw new BytecodeException("invalid access type [" + strAccess
                        + "], access types are remote, public, package, private", getStart());
            access = acc;

        }

        else if ("output".equals(name))
            this.output = toLitBoolean(name, attr.getValue());
        else if ("bufferoutput".equals(name))
            this.bufferOutput = toLitBoolean(name, attr.getValue());
        else if ("displayname".equals(name))
            this.displayName = toLitString(name, attr.getValue());
        else if ("hint".equals(name))
            this.hint = toLitString(name, attr.getValue());
        else if ("description".equals(name))
            this.description = toLitString(name, attr.getValue());
        else if ("returnformat".equals(name))
            this.returnFormat = toLitString(name, attr.getValue());
        else if ("securejson".equals(name))
            this.secureJson = toLitBoolean(name, attr.getValue());
        else if ("verifyclient".equals(name))
            this.verifyClient = toLitBoolean(name, attr.getValue());
        else if ("localmode".equals(name)) {
            Expression v = attr.getValue();
            if (v != null) {
                String str = ASMUtil.toString(v, null);
                if (!StringUtil.isEmpty(str)) {
                    int mode = AppListenerUtil.toLocalMode(str, -1);
                    if (mode != -1)
                        this.localMode = LitInteger.toExpr(mode);
                    else
                        throw new BytecodeException(
                                "Attribute localMode of the Tag Function, must be a literal value (modern, classic, true or false)",
                                getStart());
                }
            }
        } else if ("cachedwithin".equals(name)) {
            try {
                this.cachedWithin = ASMUtil.cachedWithinValue(attr.getValue());//ASMUtil.timeSpanToLong(attr.getValue());
            } catch (EvaluatorException e) {
                throw new TemplateException(e.getMessage());
            }
        } else if ("modifier".equals(name)) {
            Expression val = attr.getValue();
            if (val instanceof Literal) {
                Literal l = (Literal) val;
                String str = StringUtil.emptyIfNull(l.getString()).trim();
                if ("abstract".equalsIgnoreCase(str))
                    _abstract = true;
                else if ("final".equalsIgnoreCase(str))
                    _final = true;
            }
        }

        else {
            toLitString(name, attr.getValue());// needed for testing
            if (metadata == null)
                metadata = new HashMap<String, Attribute>();
            metadata.put(attr.getName(), attr);
        }
    }

    private final LitString toLitString(String name, Expression value) throws BytecodeException {
        ExprString es = CastString.toExprString(value);
        if (!(es instanceof LitString))
            throw new BytecodeException("value of attribute [" + name + "] must have a literal/constant value",
                    getStart());
        return (LitString) es;
    }

    private final LitBoolean toLitBoolean(String name, Expression value) throws BytecodeException {
        ExprBoolean eb = CastBoolean.toExprBoolean(value);
        if (!(eb instanceof LitBoolean))
            throw new BytecodeException("value of attribute [" + name + "] must have a literal/constant value",
                    getStart());
        return (LitBoolean) eb;
    }

    private final ExprInt toLitInt(String name, Expression value) throws BytecodeException {
        ExprInt eb = CastInt.toExprInt(value);
        if (!(eb instanceof Literal))
            throw new BytecodeException("value of attribute [" + name + "] must have a literal/constant value",
                    getStart());
        return eb;
    }

}