Open Source Repository

Home /freemarker/freemarker-2.3.16 | Repository Home



freemarker/core/BuiltIn.java
/*
 * Copyright (c) 2003 The Visigoth Software Society. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Visigoth Software Society (http://www.visigoths.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
 *    project contributors may be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [email protected].
 *
 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
 *    nor may "FreeMarker" or "Visigoth" appear in their names
 *    without prior written permission of the Visigoth Software Society.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Visigoth Software Society. For more
 * information on the Visigoth Software Society, please see
 * http://www.visigoths.org/
 */

package freemarker.core;

import freemarker.template.*;

import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.StringUtil;
import freemarker.core.NodeBuiltins.*;
import freemarker.core.NumericalBuiltins.*;
import freemarker.core.SequenceBuiltins.*;
import freemarker.core.StringBuiltins.*;

/**
 * The ? operator used to get the
 * functionality of built-in unary operators
 @author <a href="mailto:[email protected]">Jonathan Revusky</a>
 */
abstract class BuiltIn extends Expression implements Cloneable {
    Expression target;
    String key;

    static final HashMap builtins = new HashMap();

    static {
        // These are the only ones we have now.
        // We throw a parse exception if it's not one of these.
        builtins.put("ancestors"new ancestorsBI());
        builtins.put("byte"new byteBI());
        builtins.put("c"new cBI());
        builtins.put("cap_first"new cap_firstBI());
        builtins.put("capitalize"new capitalizeBI());
        builtins.put("ceiling"new ceilingBI());
        builtins.put("children"new childrenBI());
        builtins.put("chop_linebreak"new chop_linebreakBI());
        builtins.put("contains"new containsBI());        
        builtins.put("date"new dateBI(TemplateDateModel.DATE));
        builtins.put("datetime"new dateBI(TemplateDateModel.DATETIME));
        builtins.put("default"new defaultBI());
        builtins.put("double"new doubleBI());
        builtins.put("ends_with"new ends_withBI());
        builtins.put("eval"new evalBI());
        builtins.put("exists"new existsBI());
        builtins.put("first"new firstBI());
        builtins.put("float"new floatBI());
        builtins.put("floor"new floorBI());
        builtins.put("chunk"new chunkBI());
        builtins.put("has_content"new has_contentBI());
        builtins.put("html"new htmlBI());
        builtins.put("if_exists"new if_existsBI());
        builtins.put("index_of"new index_ofBI());
        builtins.put("int"new intBI());
        builtins.put("interpret"new Interpret());
        builtins.put("is_boolean"new is_booleanBI());
        builtins.put("is_collection"new is_collectionBI());
        builtins.put("is_date"new is_dateBI());
        builtins.put("is_directive"new is_directiveBI());
        builtins.put("is_enumerable"new is_enumerableBI());
        builtins.put("is_hash_ex"new is_hash_exBI());
        builtins.put("is_hash"new is_hashBI());
        builtins.put("is_indexable"new is_indexableBI());
        builtins.put("is_macro"new is_macroBI());
        builtins.put("is_method"new is_methodBI());
        builtins.put("is_node"new is_nodeBI());
        builtins.put("is_number"new is_numberBI());
        builtins.put("is_sequence"new is_sequenceBI());
        builtins.put("is_string"new is_stringBI());
        builtins.put("is_transform"new is_transformBI());
        builtins.put("j_string"new j_stringBI());
        builtins.put("js_string"new js_stringBI());
        builtins.put("keys"new keysBI());
        builtins.put("last_index_of"new last_index_ofBI());
        builtins.put("last"new lastBI());
        builtins.put("left_pad"new left_padBI());
        builtins.put("length"new lengthBI());
        builtins.put("long"new longBI());
        builtins.put("lower_case"new lower_caseBI());
        builtins.put("namespace"new namespaceBI());
        builtins.put("new"new NewBI());
        builtins.put("node_name"new node_nameBI());
        builtins.put("node_namespace"new node_namespaceBI());
        builtins.put("node_type"new node_typeBI());
        builtins.put("number"new numberBI());
        builtins.put("parent"new parentBI());
        builtins.put("replace"new replaceBI());
        builtins.put("reverse"new reverseBI());
        builtins.put("right_pad"new right_padBI());
        builtins.put("root"new rootBI());
        builtins.put("round"new roundBI());
        builtins.put("rtf"new rtfBI());
        builtins.put("seq_contains"new seq_containsBI());
        builtins.put("seq_index_of"new seq_index_ofBI(1));
        builtins.put("seq_last_index_of"new seq_index_ofBI(-1));
        builtins.put("short"new shortBI());
        builtins.put("size"new sizeBI());
        builtins.put("sort_by"new sort_byBI());
        builtins.put("sort"new sortBI());
        builtins.put("split"new splitBI());
        builtins.put("starts_with"new starts_withBI());
        builtins.put("string"new stringBI());
  builtins.put("substring"new substringBI());
        builtins.put("time"new dateBI(TemplateDateModel.TIME));
        builtins.put("trim"new trimBI());
        builtins.put("uncap_first"new uncap_firstBI());
        builtins.put("upper_case"new upper_caseBI());
        builtins.put("url"new urlBI());
        builtins.put("values"new valuesBI());
        builtins.put("web_safe", builtins.get("html"));  // deprecated; use ?html instead
        builtins.put("word_list"new word_listBI());
        builtins.put("xhtml"new xhtmlBI());
        builtins.put("xml"new xmlBI());
        try {
            Class.forName("java.util.regex.Pattern");
            builtins.put("matches", instantiate("freemarker.core.RegexBuiltins$matchesBI"));
            builtins.put("groups", instantiate("freemarker.core.RegexBuiltins$groupsBI"));
            builtins.put("replace", instantiate("freemarker.core.RegexBuiltins$replace_reBI"));
            builtins.put("split", instantiate("freemarker.core.RegexBuiltins$split_reBI"));
        catch (Exception e) {}
    }

    private static Object instantiate(String classNamethrows Exception
    {
        return ClassUtil.forName(className).newInstance();
    }
    
    static BuiltIn newBuiltIn(Expression target, String key, Token tok, String templateNamethrows ParseException {
        BuiltIn bi = (BuiltInbuiltins.get(key);
        if (bi == null) {
            String locationInfo = "Error on line " + tok.beginLine + ", column " + tok.beginColumn + ", in template " + templateName + "\n";
            StringBuffer buf = new StringBuffer("Found " + key + ", expecting one of: ");
            for (Iterator it= builtins.keySet().iterator(); it.hasNext();) {
                if (it.hasNext()) {
                    buf.append(" ");
                else {
                    buf.append" or ");
                }
                buf.append(it.next());
                if (it.hasNext()) {
                    buf.append(", ");
                }
            }
            throw new ParseException(locationInfo + buf, target);
        }
        try {
            bi = (BuiltInbi.clone();
        }
        catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
        bi.target = target;
        bi.key = key;
        return bi;
    }

    public String getCanonicalForm() {
        return target.getCanonicalForm() "?" + key;
    }

    boolean isLiteral() {
        return false// be on the safe side.
    }

    Expression _deepClone(String name, Expression subst) {
      try {
        BuiltIn clone = (BuiltIn)clone();
        clone.target = target.deepClone(name, subst);
        return clone;
        }
        catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
    }


    static class lengthBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
        throws TemplateException
        {
            return new SimpleNumber(target.getStringValue(env).length());
        }
    }

    static class dateBI extends BuiltIn {
        private final int dateType;
        
        dateBI(int dateType) {
            this.dateType = dateType;
        }
        
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException
        {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateDateModel) {
                TemplateDateModel dmodel = (TemplateDateModel)model;
                int dtype = dmodel.getDateType();
                // Any date model can be coerced into its own type
                if(dateType == dtype) {
                    return model;
                }
                // unknown and datetime can be coerced into any date type
                if(dtype == TemplateDateModel.UNKNOWN || dtype == TemplateDateModel.DATETIME) {
                    return new SimpleDate(dmodel.getAsDate(), dateType);
                }
                throw new TemplateException(
                    "Cannot convert " + TemplateDateModel.TYPE_NAMES.get(dtype)
                    " into " + TemplateDateModel.TYPE_NAMES.get(dateType), env);
            }
            // Otherwise, interpret as a string and attempt 
            // to parse it into a date.
            String s = target.getStringValue(env);
            return new DateParser(s, env);
        }
        
        private class DateParser
        implements
            TemplateDateModel,
            TemplateMethodModel,
            TemplateHashModel
        {
            private final String text;
            private final Environment env;
            private final DateFormat defaultFormat;
            private Date cachedValue;
            
            DateParser(String text, Environment env)
            throws
                TemplateModelException
            {
                this.text = text;
                this.env = env;
                this.defaultFormat = env.getDateFormatObject(dateType);
            }
            
            public Date getAsDate() throws TemplateModelException {
                if(cachedValue == null) {
                    cachedValue = parse(defaultFormat);
                }
                return cachedValue;
            }
            
            public int getDateType() {
                return dateType;
            }

            public TemplateModel get(String patternthrows TemplateModelException {
                return new SimpleDate(
                    parse(env.getDateFormatObject(dateType, pattern)),
                    dateType);
            }

            public Object exec(List arguments)
                throws TemplateModelException {
                if (arguments.size() != 1) {
                    throw new TemplateModelException(
                            "string?" + key + "(...) requires exactly 1 argument.");
                }
                return get((Stringarguments.get(0));
            }

            public boolean isEmpty()
            {
                return false;
            }

            private Date parse(DateFormat df)
            throws
                TemplateModelException
            {
                try {
                    return df.parse(text);
                }
                catch(java.text.ParseException e) {
                    String mess = "Error: " + getStartLocation()
                                 "\nExpecting a date here, found: " + text;
                    throw new TemplateModelException(mess);
                }
            }
        }
    }

    static class stringBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException
        {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateNumberModel) {
                return new NumberFormatter(EvaluationUtil.getNumber((TemplateNumberModel)model, target, env), env);
            }
            if (model instanceof TemplateDateModel) {
                TemplateDateModel dm = (TemplateDateModel)model;
                int dateType = dm.getDateType();
                return new DateFormatter(EvaluationUtil.getDate(dm, target, env), dateType, env);
            }
            if (model instanceof SimpleScalar) {
                return model;
            }
            if (model instanceof TemplateBooleanModel) {
                return new BooleanFormatter((TemplateBooleanModelmodel, env);
            }
            if (model instanceof TemplateScalarModel) {
                return new SimpleScalar(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "number, date, or string");
        }

        private static class NumberFormatter
        implements
            TemplateScalarModel,
            TemplateHashModel,
            TemplateMethodModel
        {
            private final Number number;
            private final Environment env;
            private final NumberFormat defaultFormat;
            private String cachedValue;

            NumberFormatter(Number number, Environment env)
            {
                this.number = number;
                this.env = env;
                defaultFormat = env.getNumberFormatObject(env.getNumberFormat());
            }

            public String getAsString()
            {
                if(cachedValue == null) {
                    cachedValue = defaultFormat.format(number);
                }
                return cachedValue;
            }

            public TemplateModel get(String key)
            {
                return new SimpleScalar(env.getNumberFormatObject(key).format(number));
            }
            
            public Object exec(List arguments)
                throws TemplateModelException {
                if (arguments.size() != 1) {
                    throw new TemplateModelException(
                            "number?string(...) requires exactly 1 argument.");
                }
                return get((Stringarguments.get(0));
            }

            public boolean isEmpty()
            {
                return false;
            }
        }
        
        private static class DateFormatter
        implements
            TemplateScalarModel,
            TemplateHashModel,
            TemplateMethodModel
        {
            private final Date date;
            private final int dateType;
            private final Environment env;
            private final DateFormat defaultFormat;
            private String cachedValue;

            DateFormatter(Date date, int dateType, Environment env)
            throws
                TemplateModelException
            {
                this.date = date;
                this.dateType = dateType;
                this.env = env;
                defaultFormat = env.getDateFormatObject(dateType);
            }

            public String getAsString()
            throws
                TemplateModelException
            {
                if(dateType == TemplateDateModel.UNKNOWN) {
                    throw new TemplateModelException("Can't convert the date to string, because it is not known which parts of the date variable are in use. Use ?date, ?time or ?datetime built-in, or ?string.<format> or ?string(format) built-in with this date.");
                }
                if(cachedValue == null) {
                    cachedValue = defaultFormat.format(date);
                }
                return cachedValue;
            }

            public TemplateModel get(String key)
            throws
                TemplateModelException
            {
                return new SimpleScalar(env.getDateFormatObject(dateType, key).format(date));
            }
            
            public Object exec(List arguments)
                throws TemplateModelException {
                if (arguments.size() != 1) {
                    throw new TemplateModelException(
                            "date?string(...) requires exactly 1 argument.");
                }
                return get((Stringarguments.get(0));
            }

            public boolean isEmpty()
            {
                return false;
            }
        }

        private static class BooleanFormatter
        implements 
            TemplateScalarModel, 
            TemplateMethodModel 
        {
            private final TemplateBooleanModel bool;
            private final Environment env;
            
            BooleanFormatter(TemplateBooleanModel bool, Environment env) {
                this.bool = bool;
                this.env = env;
            }

            public String getAsString() throws TemplateModelException {
                if (bool instanceof TemplateScalarModel) {
                    return ((TemplateScalarModelbool).getAsString();
                else {
                    return env.getBooleanFormat(bool.getAsBoolean());
                }
            }

            public Object exec(List arguments)
                    throws TemplateModelException {
                if (arguments.size() != 2) {
                    throw new TemplateModelException(
                            "boolean?string(...) requires exactly "
                            "2 arguments.");
                }
                return new SimpleScalar(
                    (Stringarguments.get(bool.getAsBoolean() 1));
            }
        }
    }

    static class trimBI extends StringBuiltIn {
        TemplateModel calculateResult(String s, Environment env) {
            return new SimpleScalar(s.trim());
        }
    }

    static class htmlBI extends StringBuiltIn {
        TemplateModel calculateResult(String s, Environment env) {
            return new SimpleScalar(StringUtil.HTMLEnc(s));
        }
    }

    static class xmlBI extends StringBuiltIn {
        TemplateModel calculateResult(String s, Environment env) {
            return new SimpleScalar(StringUtil.XMLEnc(s));
        }
    }

    static class xhtmlBI extends StringBuiltIn {
        TemplateModel calculateResult(String s, Environment env) {
            return new SimpleScalar(StringUtil.XHTMLEnc(s));
        }
    }

    static class rtfBI extends StringBuiltIn {
        TemplateModel calculateResult(String s, Environment env) {
            return new SimpleScalar(StringUtil.RTFEnc(s));
        }
    }

    static class urlBI extends StringBuiltIn {
        
        TemplateModel calculateResult(String s, Environment env) {
            return new urlBIResult(s, env);
        }
        
        static class urlBIResult implements
                TemplateScalarModel, TemplateMethodModel {
            
            private final String target;
            private final Environment env;
            private String cachedResult;

            private urlBIResult(String target, Environment env) {
                this.target = target;
                this.env = env;
            }
            
            public String getAsString() throws TemplateModelException {
                if (cachedResult == null) {
                    String cs = env.getEffectiveURLEscapingCharset();
                    if (cs == null) {
                        throw new TemplateModelException(
                                "To do URL encoding, the framework that encloses "
                                "FreeMarker must specify the output encoding "
                                "or the URL encoding charset, so ask the "
                                "programmers to fix it. Or, as a last chance, "
                                "you can set the url_encoding_charset setting in "
                                "the template, e.g. "
                                "<#setting url_escaping_charset='ISO-8859-1'>, or "
                                "give the charset explicitly to the buit-in, e.g. "
                                "foo?url('ISO-8859-1').");
                    }
                    try {
                        cachedResult = StringUtil.URLEnc(target, cs);
                    catch (UnsupportedEncodingException e) {
                        throw new TemplateModelException(
                                "Failed to execute URL encoding.", e);
                    }
                }
                return cachedResult;
            }

            public Object exec(List argsthrows TemplateModelException {
                if (args.size() != 1) {
                    throw new TemplateModelException("The \"url\" built-in "
                            "needs exactly 1 parameter, the charset.");
                }
                try {
                    return new SimpleScalar(
                            StringUtil.URLEnc(target, (Stringargs.get(0)));
                catch (UnsupportedEncodingException e) {
                    throw new TemplateModelException(
                            "Failed to execute URL encoding.", e);
                }
            }
            
        }
    }
    
    static class keysBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException
        {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateHashModelEx) {
                TemplateCollectionModel keys = ((TemplateHashModelExmodel).keys();
                assertNonNull(keys, this, env);
                if (!(keys instanceof TemplateSequenceModel))
                    keys = new CollectionAndSequence(keys);
                return keys;
            }
            throw invalidTypeException(model, target, env, "extended hash");
        }
    }

    static class valuesBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException
        {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateHashModelEx) {
                TemplateCollectionModel values = ((TemplateHashModelExmodel).values();
                assertNonNull(values, this, env);
                if (!(values instanceof TemplateSequenceModel))
                    values = new CollectionAndSequence(values);
                return values;
            }
            throw invalidTypeException(model, target, env, "extended hash");
        }
    }

    static class sizeBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException
        {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateSequenceModel) {
                int size = ((TemplateSequenceModelmodel).size();
                return new SimpleNumber(size);
            }
            if (model instanceof TemplateHashModelEx) {
                int size = ((TemplateHashModelExmodel).size();
                return new SimpleNumber(size);
            }
            throw invalidTypeException(model, target, env, "extended-hash or sequence");
        }
    }

    static class existsBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException
        {
            try {
                TemplateModel model = target.getAsTemplateModel(env);
                return model==null ? TemplateBooleanModel.FALSE : TemplateBooleanModel.TRUE;
            catch (InvalidReferenceException ire) {
                if (target instanceof ParentheticalExpression) {
                    return TemplateBooleanModel.FALSE;
                }
                throw ire;
            }
        }

        boolean isTrue(Environment envthrows TemplateException {
            return _getAsTemplateModel(env== TemplateBooleanModel.TRUE;
        }
    }

    static class has_contentBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException
        {
            try {
                TemplateModel model = target.getAsTemplateModel(env);
                return Expression.isEmpty(model?
                    TemplateBooleanModel.FALSE : TemplateBooleanModel.TRUE;
            catch (InvalidReferenceException ire) {
                if (target instanceof ParentheticalExpression) {
                    return TemplateBooleanModel.FALSE;
                }
                throw ire;
            }
        }

        boolean isTrue(Environment envthrows TemplateException {
            return _getAsTemplateModel(env== TemplateBooleanModel.TRUE;
        }
    }

    static class if_existsBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException
        {
            try {
                TemplateModel model = target.getAsTemplateModel(env);
                return model == null ? TemplateModel.NOTHING : model;
            catch (InvalidReferenceException ire) {
                if (target instanceof ParentheticalExpression) {
                    return TemplateModel.NOTHING;
                }
                throw ire;
            }
        }
    }

    static class is_stringBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateScalarModel)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_numberBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateNumberModel)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_nodeBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateNodeModel)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_booleanBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateBooleanModel)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_dateBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateDateModel)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_methodBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateMethodModel)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_macroBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof Macro)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_transformBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateTransformModel)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_hashBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateHashModel? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_hash_exBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateHashModelEx? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_sequenceBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateSequenceModel? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_collectionBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateCollectionModel? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_indexableBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateSequenceModel? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_enumerableBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)  ?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class is_directiveBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            assertNonNull(tm, target, env);
            return (tm instanceof TemplateTransformModel || tm instanceof Macro || tm instanceof TemplateDirectiveModel?
                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
        }
    }

    static class namespaceBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment envthrows TemplateException {
            TemplateModel tm = target.getAsTemplateModel(env);
            if (!(tm instanceof Macro)) {
                invalidTypeException(tm, target, env, "macro");
            }
            return env.getMacroNamespace((Macrotm);
        }
    }

    static class defaultBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(final Environment env)
                throws TemplateException
        {
            try {
                TemplateModel model = target.getAsTemplateModel(env);
                return
                    model == null
                    ? FIRST_NON_NULL_METHOD
                    new ConstantMethod(model);
            catch (InvalidReferenceException ire) {
                if (target instanceof ParentheticalExpression) {
                    return FIRST_NON_NULL_METHOD;
                }
                throw ire;
            }
        }

        private static class ConstantMethod implements TemplateMethodModelEx
        {
            private final TemplateModel constant;

            ConstantMethod(TemplateModel constant) {
                this.constant = constant;
            }

            public Object exec(List args) {
                return constant;
            }
        }

        /**
         * A method that goes through the arguments one by one and returns
         * the first one that is non-null. If all args are null, returns null.
         */
        private static final TemplateMethodModelEx FIRST_NON_NULL_METHOD =
            new TemplateMethodModelEx() {
                public Object exec(List argsthrows TemplateModelException {
                    if(args.isEmpty()) {
                        throw new TemplateModelException(
                            "?default(arg) expects at least one argument.");
                    }
                    TemplateModel result = null;
                    for (int i = 0; i< args.size(); i++ ) {
                        result = (TemplateModelargs.get(i);
                        if (result != null) {
                            break;
                        }
                    }
                    return result;
                }
            };
    }

    static class containsBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }

        private static class BIMethod implements TemplateMethodModelEx {
            private String s;

            private BIMethod(String s) {
                this.s = s;
            }

            public Object exec(List argsthrows TemplateModelException {
                Object obj;
                String sub;

                int ln  = args.size();
                if (ln != 1) {
                    throw new TemplateModelException(
                            "?contains(...) expects one argument.");
                }

                obj = args.get(0);
                if (!(obj instanceof TemplateScalarModel)) {
                    throw new TemplateModelException(
                            "?contains(...) expects a string as "
                            "its first argument.");
                }
                sub = ((TemplateScalarModelobj).getAsString();

                return
                    (s.indexOf(sub!= -1?
                    TemplateBooleanModel.TRUE :
                    TemplateBooleanModel.FALSE;
            }
        }
    }
    
    static class index_ofBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }
        
        private static class BIMethod implements TemplateMethodModelEx {
            private String s;
            
            private BIMethod(String s) {
                this.s = s;
            }
            
            public Object exec(List argsthrows TemplateModelException {
                Object obj;
                String sub;
                int fidx;

                int ln  = args.size();
                if (ln == 0) {
                    throw new TemplateModelException(
                            "?index_of(...) expects at least one argument.");
                }
                if (ln > 2) {
                    throw new TemplateModelException(
                            "?index_of(...) expects at most two arguments.");
                }

                obj = args.get(0);       
                if (!(obj instanceof TemplateScalarModel)) {
                    throw new TemplateModelException(
                            "?index_of(...) expects a string as "
                            "its first argument.");
                }
                sub = ((TemplateScalarModelobj).getAsString();
                
                if (ln > 1) {
                    obj = args.get(1);
                    if (!(obj instanceof TemplateNumberModel)) {
                        throw new TemplateModelException(
                                "?index_of(...) expects a number as "
                                "its second argument.");
                    }
                    fidx = ((TemplateNumberModelobj).getAsNumber().intValue();
                else {
                    fidx = 0;
                }

                return new SimpleNumber(s.indexOf(sub, fidx));
            }
        
    }

    static class last_index_ofBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }

        private static class BIMethod implements TemplateMethodModelEx {
            private String s;

            private BIMethod(String s) {
                this.s = s;
            }

            public Object exec(List argsthrows TemplateModelException {
                Object obj;
                String sub;

                int ln  = args.size();
                if (ln == 0) {
                    throw new TemplateModelException(
                            "?last_index_of(...) expects at least one argument.");
                }
                if (ln > 2) {
                    throw new TemplateModelException(
                            "?last_index_of(...) expects at most two arguments.");
                }

                obj = args.get(0);
                if (!(obj instanceof TemplateScalarModel)) {
                    throw new TemplateModelException(
                            "?last_index_of(...) expects a string as "
                            "its first argument.");
                }
                sub = ((TemplateScalarModelobj).getAsString();

                if (ln > 1) {
                    obj = args.get(1);
                    if (!(obj instanceof TemplateNumberModel)) {
                        throw new TemplateModelException(
                                "?last_index_of(...) expects a number as "
                                "its second argument.");
                    }
                    int fidx = ((TemplateNumberModelobj).getAsNumber().intValue();
                    return new SimpleNumber(s.lastIndexOf(sub, fidx));
                else {
                    return new SimpleNumber(s.lastIndexOf(sub));
                }
            }
        }
    }
    
    static class starts_withBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }

        private static class BIMethod implements TemplateMethodModelEx {
            private String s;

            private BIMethod(String s) {
                this.s = s;
            }

            public Object exec(List argsthrows TemplateModelException {
                String sub;

                if (args.size() != 1) {
                    throw new TemplateModelException(
                            "?starts_with(...) expects exactly 1 argument.");
                }

                Object obj = args.get(0);
                if (!(obj instanceof TemplateScalarModel)) {
                    throw new TemplateModelException(
                            "?starts_with(...) expects a string argument");
                }
                sub = ((TemplateScalarModelobj).getAsString();

                return s.startsWith(sub?
                        TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
            }
        }
    }

    static class ends_withBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }

        private static class BIMethod implements TemplateMethodModelEx {
            private String s;

            private BIMethod(String s) {
                this.s = s;
            }

            public Object exec(List argsthrows TemplateModelException {
                String sub;

                if (args.size() != 1) {
                    throw new TemplateModelException(
                            "?ends_with(...) expects exactly 1 argument.");
                }

                Object obj = args.get(0);
                if (!(obj instanceof TemplateScalarModel)) {
                    throw new TemplateModelException(
                            "?ends_with(...) expects a string argument");
                }
                sub = ((TemplateScalarModelobj).getAsString();

                return s.endsWith(sub?
                        TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
            }
        }
    }
    
    static class replaceBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }

        private static class BIMethod implements TemplateMethodModel {
            private String s;

            private BIMethod(String s) {
                this.s = s;
            }

            public Object exec(List argsthrows TemplateModelException {
                int numArgs = args.size();
                if (numArgs < || numArgs > 3) {
                    throw new TemplateModelException(
                            "?replace(...) needs 2 or 3 arguments.");
                }
                String first = (Stringargs.get(0);
                String second = (Stringargs.get(1);
                String flags = numArgs >(Stringargs.get(2"";
                boolean caseInsensitive = flags.indexOf('i'>=0;
                boolean firstOnly = flags.indexOf('f'>=0;
                if (flags.indexOf('r'>=0) {
                    throw new TemplateModelException("The regular expression classes are not available.");
                }
                return new SimpleScalar(StringUtil.replace(
                        s, first, second, caseInsensitive, firstOnly));
            }
        }
    }

    static class splitBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }

        private static class BIMethod implements TemplateMethodModel {
            private String s;

            private BIMethod(String s) {
                this.s = s;
            }

            public Object exec(List argsthrows TemplateModelException {
                int numArgs = args.size();
                if (numArgs != && numArgs !=2) {
                    throw new TemplateModelException(
                            "?split(...) expects 1 or 2 arguments.");
                }
                String splitString = (Stringargs.get(0);
                String flags = numArgs ==(Stringargs.get(1"";
                boolean caseInsensitive = flags.indexOf('i'>=0;
                if (flags.indexOf('r'>=0) {
                    throw new TemplateModelException("regular expression classes not available");
                }
                return new StringArraySequence(StringUtil.split(
                        s, splitString, caseInsensitive));
            }
        }
    }

    static class left_padBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }

        private static class BIMethod implements TemplateMethodModelEx {
            private String s;

            private BIMethod(String s) {
                this.s = s;
            }

            public Object exec(List argsthrows TemplateModelException {
                Object obj;

                int ln  = args.size();
                if (ln == 0) {
                    throw new TemplateModelException(
                            "?left_pad(...) expects at least 1 argument.");
                }
                if (ln > 2) {
                    throw new TemplateModelException(
                            "?left_pad(...) expects at most 2 arguments.");
                }

                obj = args.get(0);
                if (!(obj instanceof TemplateNumberModel)) {
                    throw new TemplateModelException(
                            "?left_pad(...) expects a number as "
                            "its 1st argument.");
                }
                int width = ((TemplateNumberModelobj).getAsNumber().intValue();

                if (ln > 1) {
                    obj = args.get(1);
                    if (!(obj instanceof TemplateScalarModel)) {
                        throw new TemplateModelException(
                                "?left_pad(...) expects a string as "
                                "its 2nd argument.");
                    }
                    String filling = ((TemplateScalarModelobj).getAsString();
                    try {
                        return new SimpleScalar(StringUtil.leftPad(s, width, filling));
                    catch (IllegalArgumentException e) {
                        if (filling.length() == 0) {
                            throw new TemplateModelException(
                                    "The 2nd argument of ?left_pad(...) "
                                    "can't be a 0 length string.");
                        else {
                            throw new TemplateModelException(
                                    "Error while executing the ?left_pad(...) "
                                    "built-in.", e);
                        }
                    }
                else {
                    return new SimpleScalar(StringUtil.leftPad(s, width));
                }
            }
        }
    }

    static class right_padBI extends BuiltIn {
        TemplateModel _getAsTemplateModel(Environment env)
                throws TemplateException {
            TemplateModel model = target.getAsTemplateModel(env);
            if (model instanceof TemplateScalarModel) {
                return new BIMethod(((TemplateScalarModelmodel).getAsString());
            }
            throw invalidTypeException(model, target, env, "string");
        }

        private static class BIMethod implements TemplateMethodModelEx {
            private String s;

            private BIMethod(String s) {
                this.s = s;
            }

            public Object exec(List argsthrows TemplateModelException {
                Object obj;

                int ln  = args.size();
                if (ln == 0) {
                    throw new TemplateModelException(
                            "?right_pad(...) expects at least 1 argument.");
                }
                if (ln > 2) {
                    throw new TemplateModelException(
                            "?right_pad(...) expects at most 2 arguments.");
                }

                obj = args.get(0);
                if (!(obj instanceof TemplateNumberModel)) {
                    throw new TemplateModelException(
                            "?right_pad(...) expects a number as "
                            "its 1st argument.");
                }
                int width = ((TemplateNumberModelobj).getAsNumber().intValue();

                if (ln > 1) {
                    obj = args.get(1);
                    if (!(obj instanceof TemplateScalarModel)) {
                        throw new TemplateModelException(
                                "?right_pad(...) expects a string as "
                                "its 2nd argument.");
                    }
                    String filling = ((TemplateScalarModelobj).getAsString();
                    try {
                        return new SimpleScalar(StringUtil.rightPad(s, width, filling));
                    catch (IllegalArgumentException e) {
                        if (filling.length() == 0) {
                            throw new TemplateModelException(
                                    "The 2nd argument of ?right_pad(...) "
                                    "can't be a 0 length string.");
                        else {
                            throw new TemplateModelException(
                                    "Error while executing the ?right_pad(...) "
                                    "built-in.", e);
                        }
                    }
                else {
                    return new SimpleScalar(StringUtil.rightPad(s, width));
                }
            }
        }
    }
    
}