freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [44/54] [partial] incubator-freemarker git commit: Unifying the o.a.f.core and o.a.f.core.ast
Date Thu, 23 Feb 2017 21:36:11 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java b/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
new file mode 100644
index 0000000..142d924
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
@@ -0,0 +1,878 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.Serializable;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.CollectionAndSequence;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.model.impl.TemplateModelListSequence;
+import org.apache.freemarker.core.model.impl.beans.CollectionModel;
+import org.apache.freemarker.core.model.Constants;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * A holder for builtins that operate exclusively on sequence or collection left-hand value.
+ */
+class BuiltInsForSequences {
+    
+    static class chunkBI extends BuiltInForSequence {
+
+        private class BIMethod implements TemplateMethodModelEx {
+            
+            private final TemplateSequenceModel tsm;
+
+            private BIMethod(TemplateSequenceModel tsm) {
+                this.tsm = tsm;
+            }
+
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                checkMethodArgCount(args, 1, 2);
+                int chunkSize = getNumberMethodArg(args, 0).intValue();
+                
+                return new ChunkedSequence(
+                        tsm,
+                        chunkSize,
+                        args.size() > 1 ? (TemplateModel) args.get(1) : null);
+            }
+        }
+
+        private static class ChunkedSequence implements TemplateSequenceModel {
+            
+            private final TemplateSequenceModel wrappedTsm;
+            
+            private final int chunkSize;
+            
+            private final TemplateModel fillerItem;
+            
+            private final int numberOfChunks;
+            
+            private ChunkedSequence(
+                    TemplateSequenceModel wrappedTsm, int chunkSize, TemplateModel fillerItem)
+                    throws TemplateModelException {
+                if (chunkSize < 1) {
+                    throw new _TemplateModelException("The 1st argument to ?', key, ' (...) must be at least 1.");
+                }
+                this.wrappedTsm = wrappedTsm;
+                this.chunkSize = chunkSize;
+                this.fillerItem = fillerItem;
+                numberOfChunks = (wrappedTsm.size() + chunkSize - 1) / chunkSize; 
+            }
+
+            @Override
+            public TemplateModel get(final int chunkIndex)
+                    throws TemplateModelException {
+                if (chunkIndex >= numberOfChunks) {
+                    return null;
+                }
+                
+                return new TemplateSequenceModel() {
+                    
+                    private final int baseIndex = chunkIndex * chunkSize;
+
+                    @Override
+                    public TemplateModel get(int relIndex)
+                            throws TemplateModelException {
+                        int absIndex = baseIndex + relIndex;
+                        if (absIndex < wrappedTsm.size()) {
+                            return wrappedTsm.get(absIndex);
+                        } else {
+                            return absIndex < numberOfChunks * chunkSize
+                                ? fillerItem
+                                : null;
+                        }
+                    }
+
+                    @Override
+                    public int size() throws TemplateModelException {
+                        return fillerItem != null || chunkIndex + 1 < numberOfChunks
+                                ? chunkSize
+                                : wrappedTsm.size() - baseIndex;
+                    }
+                    
+                };
+            }
+
+            @Override
+            public int size() throws TemplateModelException {
+                return numberOfChunks;
+            }
+            
+        }
+        
+        @Override
+        TemplateModel calculateResult(TemplateSequenceModel tsm) throws TemplateModelException {
+            return new BIMethod(tsm);
+        }
+        
+    }
+    
+    static class firstBI extends ASTExpBuiltIn {
+        
+        @Override
+        TemplateModel _eval(Environment env)
+                throws TemplateException {
+            TemplateModel model = target.eval(env);
+            // In 2.3.x only, we prefer TemplateSequenceModel for
+            // backward compatibility. In 2.4.x, we prefer TemplateCollectionModel. 
+            if (model instanceof TemplateSequenceModel && !isBuggySeqButGoodCollection(model)) {
+                return calculateResultForSequence((TemplateSequenceModel) model);
+            } else if (model instanceof TemplateCollectionModel) {
+                return calculateResultForColletion((TemplateCollectionModel) model);
+            } else {
+                throw new NonSequenceOrCollectionException(target, model, env);
+            }
+        }        
+        
+        private TemplateModel calculateResultForSequence(TemplateSequenceModel seq)
+        throws TemplateModelException {
+            if (seq.size() == 0) {
+                return null;
+            }
+            return seq.get(0);
+        }
+        
+        private TemplateModel calculateResultForColletion(TemplateCollectionModel coll)
+        throws TemplateModelException {
+            TemplateModelIterator iter = coll.iterator();
+            if (!iter.hasNext()) {
+                return null;
+            }
+            return iter.next();
+        }
+        
+    }
+
+    static class joinBI extends ASTExpBuiltIn {
+        
+        private class BIMethodForCollection implements TemplateMethodModelEx {
+            
+            private final Environment env;
+            private final TemplateCollectionModel coll;
+
+            private BIMethodForCollection(Environment env, TemplateCollectionModel coll) {
+                this.env = env;
+                this.coll = coll;
+            }
+
+            @Override
+            public Object exec(List args)
+                    throws TemplateModelException {
+                checkMethodArgCount(args, 1, 3);
+                final String separator = getStringMethodArg(args, 0);
+                final String whenEmpty = getOptStringMethodArg(args, 1);
+                final String afterLast = getOptStringMethodArg(args, 2);
+                
+                StringBuilder sb = new StringBuilder();
+                
+                TemplateModelIterator it = coll.iterator();
+                
+                int idx = 0;
+                boolean hadItem = false;
+                while (it.hasNext()) {
+                    TemplateModel item = it.next();
+                    if (item != null) {
+                        if (hadItem) {
+                            sb.append(separator);
+                        } else {
+                            hadItem = true;
+                        }
+                        try {
+                            sb.append(EvalUtil.coerceModelToStringOrUnsupportedMarkup(item, null, null, env));
+                        } catch (TemplateException e) {
+                            throw new _TemplateModelException(e,
+                                    "\"?", key, "\" failed at index ", Integer.valueOf(idx), " with this error:\n\n",
+                                    MessageUtil.EMBEDDED_MESSAGE_BEGIN,
+                                    new _DelayedGetMessageWithoutStackTop(e),
+                                    MessageUtil.EMBEDDED_MESSAGE_END);
+                        }
+                    }
+                    idx++;
+                }
+                if (hadItem) {
+                    if (afterLast != null) sb.append(afterLast);
+                } else {
+                    if (whenEmpty != null) sb.append(whenEmpty);
+                }
+                return new SimpleScalar(sb.toString());
+           }
+
+        }
+
+        @Override
+        TemplateModel _eval(Environment env) throws TemplateException {
+            TemplateModel model = target.eval(env);
+            if (model instanceof TemplateCollectionModel) {
+                if (model instanceof RightUnboundedRangeModel) {
+                    throw new _TemplateModelException(
+                            "The sequence to join was right-unbounded numerical range, thus it's infinitely long.");
+                }
+                return new BIMethodForCollection(env, (TemplateCollectionModel) model);
+            } else if (model instanceof TemplateSequenceModel) {
+                return new BIMethodForCollection(env, new CollectionAndSequence((TemplateSequenceModel) model));
+            } else {
+                throw new NonSequenceOrCollectionException(target, model, env);
+            }
+        }
+   
+    }
+
+    static class lastBI extends BuiltInForSequence {
+        @Override
+        TemplateModel calculateResult(TemplateSequenceModel tsm)
+        throws TemplateModelException {
+            if (tsm.size() == 0) {
+                return null;
+            }
+            return tsm.get(tsm.size() - 1);
+        }
+    }
+
+    static class reverseBI extends BuiltInForSequence {
+        private static class ReverseSequence implements TemplateSequenceModel {
+            private final TemplateSequenceModel seq;
+
+            ReverseSequence(TemplateSequenceModel seq) {
+                this.seq = seq;
+            }
+
+            @Override
+            public TemplateModel get(int index) throws TemplateModelException {
+                return seq.get(seq.size() - 1 - index);
+            }
+
+            @Override
+            public int size() throws TemplateModelException {
+                return seq.size();
+            }
+        }
+
+        @Override
+        TemplateModel calculateResult(TemplateSequenceModel tsm) {
+            if (tsm instanceof ReverseSequence) {
+                return ((ReverseSequence) tsm).seq;
+            } else {
+                return new ReverseSequence(tsm);
+            }
+        }
+    }
+
+    static class seq_containsBI extends ASTExpBuiltIn {
+        private class BIMethodForCollection implements TemplateMethodModelEx {
+            private TemplateCollectionModel m_coll;
+            private Environment m_env;
+
+            private BIMethodForCollection(TemplateCollectionModel coll, Environment env) {
+                m_coll = coll;
+                m_env = env;
+            }
+
+            @Override
+            public Object exec(List args)
+                    throws TemplateModelException {
+                checkMethodArgCount(args, 1);
+                TemplateModel arg = (TemplateModel) args.get(0);
+                TemplateModelIterator it = m_coll.iterator();
+                int idx = 0;
+                while (it.hasNext()) {
+                    if (modelsEqual(idx, it.next(), arg, m_env))
+                        return TemplateBooleanModel.TRUE;
+                    idx++;
+                }
+                return TemplateBooleanModel.FALSE;
+            }
+
+        }
+
+        private class BIMethodForSequence implements TemplateMethodModelEx {
+            private TemplateSequenceModel m_seq;
+            private Environment m_env;
+
+            private BIMethodForSequence(TemplateSequenceModel seq, Environment env) {
+                m_seq = seq;
+                m_env = env;
+            }
+
+            @Override
+            public Object exec(List args)
+                    throws TemplateModelException {
+                checkMethodArgCount(args, 1);
+                TemplateModel arg = (TemplateModel) args.get(0);
+                int size = m_seq.size();
+                for (int i = 0; i < size; i++) {
+                    if (modelsEqual(i, m_seq.get(i), arg, m_env))
+                        return TemplateBooleanModel.TRUE;
+                }
+                return TemplateBooleanModel.FALSE;
+            }
+
+        }
+    
+        @Override
+        TemplateModel _eval(Environment env)
+                throws TemplateException {
+            TemplateModel model = target.eval(env);
+            // In 2.3.x only, we prefer TemplateSequenceModel for
+            // backward compatibility. In 2.4.x, we prefer TemplateCollectionModel. 
+            if (model instanceof TemplateSequenceModel && !isBuggySeqButGoodCollection(model)) {
+                return new BIMethodForSequence((TemplateSequenceModel) model, env);
+            } else if (model instanceof TemplateCollectionModel) {
+                return new BIMethodForCollection((TemplateCollectionModel) model, env);
+            } else {
+                throw new NonSequenceOrCollectionException(target, model, env);
+            }
+        }
+    
+    }
+    
+    static class seq_index_ofBI extends ASTExpBuiltIn {
+        
+        private class BIMethod implements TemplateMethodModelEx {
+            
+            protected final TemplateSequenceModel m_seq;
+            protected final TemplateCollectionModel m_col;
+            protected final Environment m_env;
+
+            private BIMethod(Environment env)
+                    throws TemplateException {
+                TemplateModel model = target.eval(env);
+                m_seq = model instanceof TemplateSequenceModel
+                            && !isBuggySeqButGoodCollection(model)
+                        ? (TemplateSequenceModel) model
+                        : null;
+                // In 2.3.x only, we deny the possibility of collection
+                // access if there's sequence access. This is so to minimize
+                // the change of compatibility issues; without this, objects
+                // that implement both the sequence and collection interfaces
+                // would suddenly start using the collection interface, and if
+                // that's buggy that would surface now, breaking the application
+                // that despite its bugs has worked earlier.
+                m_col = m_seq == null && model instanceof TemplateCollectionModel
+                        ? (TemplateCollectionModel) model
+                        : null;
+                if (m_seq == null && m_col == null) {
+                    throw new NonSequenceOrCollectionException(target, model, env);
+                }
+                
+                m_env = env;
+            }
+
+            @Override
+            public final Object exec(List args)
+                    throws TemplateModelException {
+                int argCnt = args.size();
+                checkMethodArgCount(argCnt, 1, 2);
+                
+                TemplateModel target = (TemplateModel) args.get(0);
+                int foundAtIdx;
+                if (argCnt > 1) {
+                    int startIndex = getNumberMethodArg(args, 1).intValue();
+                    // In 2.3.x only, we prefer TemplateSequenceModel for
+                    // backward compatibility:
+                    foundAtIdx = m_seq != null
+                            ? findInSeq(target, startIndex)
+                            : findInCol(target, startIndex);
+                } else {
+                    // In 2.3.x only, we prefer TemplateSequenceModel for
+                    // backward compatibility:
+                    foundAtIdx = m_seq != null
+                            ? findInSeq(target)
+                            : findInCol(target);
+                }
+                return foundAtIdx == -1 ? Constants.MINUS_ONE : new SimpleNumber(foundAtIdx);
+            }
+            
+            int findInCol(TemplateModel target) throws TemplateModelException {
+                return findInCol(target, 0, Integer.MAX_VALUE);
+            }
+            
+            protected int findInCol(TemplateModel target, int startIndex)
+                    throws TemplateModelException {
+                if (m_dir == 1) {
+                    return findInCol(target, startIndex, Integer.MAX_VALUE);
+                } else {
+                    return findInCol(target, 0, startIndex);
+                }
+            }
+        
+            protected int findInCol(TemplateModel target,
+                    final int allowedRangeStart, final int allowedRangeEnd)
+                    throws TemplateModelException {
+                if (allowedRangeEnd < 0) return -1;
+                
+                TemplateModelIterator it = m_col.iterator();
+                
+                int foundAtIdx = -1;  // -1 is the return value for "not found"
+                int idx = 0; 
+                searchItem: while (it.hasNext()) {
+                    if (idx > allowedRangeEnd) break searchItem;
+                    
+                    TemplateModel current = it.next();
+                    if (idx >= allowedRangeStart) {
+                        if (modelsEqual(idx, current, target, m_env)) {
+                            foundAtIdx = idx;
+                            if (m_dir == 1) break searchItem; // "find first"
+                            // Otherwise it's "find last".
+                        }
+                    }
+                    idx++;
+                }
+                return foundAtIdx;
+            }
+
+            int findInSeq(TemplateModel target)
+            throws TemplateModelException {
+                final int seqSize = m_seq.size();
+                final int actualStartIndex;
+                
+                if (m_dir == 1) {
+                    actualStartIndex = 0;
+                } else {
+                    actualStartIndex = seqSize - 1;
+                }
+            
+                return findInSeq(target, actualStartIndex, seqSize); 
+            }
+
+            private int findInSeq(TemplateModel target, int startIndex)
+                    throws TemplateModelException {
+                int seqSize = m_seq.size();
+                
+                if (m_dir == 1) {
+                    if (startIndex >= seqSize) {
+                        return -1;
+                    }
+                    if (startIndex < 0) {
+                        startIndex = 0;
+                    }
+                } else {
+                    if (startIndex >= seqSize) {
+                        startIndex = seqSize - 1;
+                    }
+                    if (startIndex < 0) {
+                        return -1;
+                    }
+                }
+                
+                return findInSeq(target, startIndex, seqSize); 
+            }
+            
+            private int findInSeq(
+                    TemplateModel target, int scanStartIndex, int seqSize)
+                    throws TemplateModelException {
+                if (m_dir == 1) {
+                    for (int i = scanStartIndex; i < seqSize; i++) {
+                        if (modelsEqual(i, m_seq.get(i), target, m_env)) return i;
+                    }
+                } else {
+                    for (int i = scanStartIndex; i >= 0; i--) {
+                        if (modelsEqual(i, m_seq.get(i), target, m_env)) return i;
+                    }
+                }
+                return -1;
+            }
+            
+        }
+
+        private int m_dir;
+
+        seq_index_ofBI(int dir) {
+            m_dir = dir;
+        }
+
+        @Override
+        TemplateModel _eval(Environment env)
+                throws TemplateException {
+            return new BIMethod(env);
+        }
+    }
+
+    static class sort_byBI extends sortBI {
+        class BIMethod implements TemplateMethodModelEx {
+            TemplateSequenceModel seq;
+            
+            BIMethod(TemplateSequenceModel seq) {
+                this.seq = seq;
+            }
+            
+            @Override
+            public Object exec(List args)
+                    throws TemplateModelException {
+                // Should be:
+                // checkMethodArgCount(args, 1);
+                // But for BC:
+                if (args.size() < 1) throw MessageUtil.newArgCntError("?" + key, args.size(), 1);
+                
+                String[] subvars;
+                Object obj = args.get(0);
+                if (obj instanceof TemplateScalarModel) {
+                    subvars = new String[]{((TemplateScalarModel) obj).getAsString()};
+                } else if (obj instanceof TemplateSequenceModel) {
+                    TemplateSequenceModel seq = (TemplateSequenceModel) obj;
+                    int ln = seq.size();
+                    subvars = new String[ln];
+                    for (int i = 0; i < ln; i++) {
+                        Object item = seq.get(i);
+                        try {
+                            subvars[i] = ((TemplateScalarModel) item)
+                                    .getAsString();
+                        } catch (ClassCastException e) {
+                            if (!(item instanceof TemplateScalarModel)) {
+                                throw new _TemplateModelException(
+                                        "The argument to ?", key, "(key), when it's a sequence, must be a "
+                                        + "sequence of strings, but the item at index ", Integer.valueOf(i),
+                                        " is not a string.");
+                            }
+                        }
+                    }
+                } else {
+                    throw new _TemplateModelException(
+                            "The argument to ?", key, "(key) must be a string (the name of the subvariable), or a "
+                            + "sequence of strings (the \"path\" to the subvariable).");
+                }
+                return sort(seq, subvars); 
+            }
+        }
+        
+        @Override
+        TemplateModel calculateResult(TemplateSequenceModel seq) {
+            return new BIMethod(seq);
+        }
+    }
+
+    static class sortBI extends BuiltInForSequence {
+        
+        private static class BooleanKVPComparator implements Comparator, Serializable {
+
+            @Override
+            public int compare(Object arg0, Object arg1) {
+                // JDK 1.2 doesn't have Boolean.compareTo
+                boolean b0 = ((Boolean) ((KVP) arg0).key).booleanValue();
+                boolean b1 = ((Boolean) ((KVP) arg1).key).booleanValue();
+                if (b0) {
+                    return b1 ? 0 : 1;
+                } else {
+                    return b1 ? -1 : 0;
+                }
+            }
+        }
+        private static class DateKVPComparator implements Comparator, Serializable {
+
+            @Override
+            public int compare(Object arg0, Object arg1) {
+                return ((Date) ((KVP) arg0).key).compareTo(
+                        (Date) ((KVP) arg1).key);
+            }
+        }
+        /**
+         * Stores a key-value pair.
+         */
+        private static class KVP {
+            private Object key;
+
+            private Object value;
+            private KVP(Object key, Object value) {
+                this.key = key;
+                this.value = value;
+            }
+        }
+        private static class LexicalKVPComparator implements Comparator {
+            private Collator collator;
+
+            LexicalKVPComparator(Collator collator) {
+                this.collator = collator;
+            }
+
+            @Override
+            public int compare(Object arg0, Object arg1) {
+                return collator.compare(
+                        ((KVP) arg0).key, ((KVP) arg1).key);
+            }
+        }
+        private static class NumericalKVPComparator implements Comparator {
+            private ArithmeticEngine ae;
+
+            private NumericalKVPComparator(ArithmeticEngine ae) {
+                this.ae = ae;
+            }
+
+            @Override
+            public int compare(Object arg0, Object arg1) {
+                try {
+                    return ae.compareNumbers(
+                            (Number) ((KVP) arg0).key,
+                            (Number) ((KVP) arg1).key);
+                } catch (TemplateException e) {
+                    throw new ClassCastException(
+                        "Failed to compare numbers: " + e);
+                }
+            }
+        }
+        
+        static TemplateModelException newInconsistentSortKeyTypeException(
+                int keyNamesLn, String firstType, String firstTypePlural, int index, TemplateModel key) {
+            String valueInMsg;
+            String valuesInMsg;
+            if (keyNamesLn == 0) {
+                valueInMsg  = "value";
+                valuesInMsg  = "values";
+            } else {
+                valueInMsg  = "key value";
+                valuesInMsg  = "key values";
+            }
+            return new _TemplateModelException(
+                    startErrorMessage(keyNamesLn, index),
+                    "All ", valuesInMsg, " in the sequence must be ",
+                    firstTypePlural, ", because the first ", valueInMsg,
+                    " was that. However, the ", valueInMsg,
+                    " of the current item isn't a ", firstType, " but a ",
+                    new _DelayedFTLTypeDescription(key), ".");
+        }
+
+        /**
+         * Sorts a sequence for the <tt>sort</tt> and <tt>sort_by</tt>
+         * built-ins.
+         * 
+         * @param seq the sequence to sort.
+         * @param keyNames the name of the subvariable whose value is used for the
+         *     sorting. If the sorting is done by a sub-subvaruable, then this
+         *     will be of length 2, and so on. If the sorting is done by the
+         *     sequene items directly, then this argument has to be 0 length
+         *     array or <code>null</code>.
+         * @return a new sorted sequence, or the original sequence if the
+         *     sequence length was 0.
+         */
+        static TemplateSequenceModel sort(TemplateSequenceModel seq, String[] keyNames)
+                throws TemplateModelException {
+            int ln = seq.size();
+            if (ln == 0) return seq;
+            
+            ArrayList res = new ArrayList(ln);
+
+            int keyNamesLn = keyNames == null ? 0 : keyNames.length;
+
+            // Copy the Seq into a Java List[KVP] (also detects key type at the 1st item):
+            int keyType = KEY_TYPE_NOT_YET_DETECTED;
+            Comparator keyComparator = null;
+            for (int i = 0; i < ln; i++) {
+                final TemplateModel item = seq.get(i);
+                TemplateModel key = item;
+                for (int keyNameI = 0; keyNameI < keyNamesLn; keyNameI++) {
+                    try {
+                        key = ((TemplateHashModel) key).get(keyNames[keyNameI]);
+                    } catch (ClassCastException e) {
+                        if (!(key instanceof TemplateHashModel)) {
+                            throw new _TemplateModelException(
+                                    startErrorMessage(keyNamesLn, i),
+                                    (keyNameI == 0
+                                            ? "Sequence items must be hashes when using ?sort_by. "
+                                            : "The " + _StringUtil.jQuote(keyNames[keyNameI - 1])),
+                                    " subvariable is not a hash, so ?sort_by ",
+                                    "can't proceed with getting the ",
+                                    new _DelayedJQuote(keyNames[keyNameI]),
+                                    " subvariable.");
+                        } else {
+                            throw e;
+                        }
+                    }
+                    if (key == null) {
+                        throw new _TemplateModelException(
+                                startErrorMessage(keyNamesLn, i),
+                                "The " + _StringUtil.jQuote(keyNames[keyNameI]), " subvariable was not found.");
+                    }
+                } // for each key
+                
+                if (keyType == KEY_TYPE_NOT_YET_DETECTED) {
+                    if (key instanceof TemplateScalarModel) {
+                        keyType = KEY_TYPE_STRING;
+                        keyComparator = new LexicalKVPComparator(
+                                Environment.getCurrentEnvironment().getCollator());
+                    } else if (key instanceof TemplateNumberModel) {
+                        keyType = KEY_TYPE_NUMBER;
+                        keyComparator = new NumericalKVPComparator(
+                                Environment.getCurrentEnvironment()
+                                        .getArithmeticEngine());
+                    } else if (key instanceof TemplateDateModel) {
+                        keyType = KEY_TYPE_DATE;
+                        keyComparator = new DateKVPComparator();
+                    } else if (key instanceof TemplateBooleanModel) {
+                        keyType = KEY_TYPE_BOOLEAN;
+                        keyComparator = new BooleanKVPComparator();
+                    } else {
+                        throw new _TemplateModelException(
+                                startErrorMessage(keyNamesLn, i),
+                                "Values used for sorting must be numbers, strings, date/times or booleans.");
+                    }
+                }
+                switch(keyType) {
+                    case KEY_TYPE_STRING:
+                        try {
+                            res.add(new KVP(
+                                    ((TemplateScalarModel) key).getAsString(),
+                                    item));
+                        } catch (ClassCastException e) {
+                            if (!(key instanceof TemplateScalarModel)) {
+                                throw newInconsistentSortKeyTypeException(
+                                        keyNamesLn, "string", "strings", i, key);
+                            } else {
+                                throw e;
+                            }
+                        }
+                        break;
+                        
+                    case KEY_TYPE_NUMBER:
+                        try {
+                            res.add(new KVP(
+                                    ((TemplateNumberModel) key).getAsNumber(),
+                                    item));
+                        } catch (ClassCastException e) {
+                            if (!(key instanceof TemplateNumberModel)) {
+                                throw newInconsistentSortKeyTypeException(
+                                        keyNamesLn, "number", "numbers", i, key);
+                            }
+                        }
+                        break;
+                        
+                    case KEY_TYPE_DATE:
+                        try {
+                            res.add(new KVP(
+                                    ((TemplateDateModel) key).getAsDate(),
+                                    item));
+                        } catch (ClassCastException e) {
+                            if (!(key instanceof TemplateDateModel)) {
+                                throw newInconsistentSortKeyTypeException(
+                                        keyNamesLn, "date/time", "date/times", i, key);
+                            }
+                        }
+                        break;
+                        
+                    case KEY_TYPE_BOOLEAN:
+                        try {
+                            res.add(new KVP(
+                                    Boolean.valueOf(((TemplateBooleanModel) key).getAsBoolean()),
+                                    item));
+                        } catch (ClassCastException e) {
+                            if (!(key instanceof TemplateBooleanModel)) {
+                                throw newInconsistentSortKeyTypeException(
+                                        keyNamesLn, "boolean", "booleans", i, key);
+                            }
+                        }
+                        break;
+                        
+                    default:
+                        throw new BugException("Unexpected key type");
+                }
+            }
+
+            // Sort tje List[KVP]:
+            try {
+                Collections.sort(res, keyComparator);
+            } catch (Exception exc) {
+                throw new _TemplateModelException(exc,
+                        startErrorMessage(keyNamesLn), "Unexpected error while sorting:" + exc);
+            }
+
+            // Convert the List[KVP] to List[V]:
+            for (int i = 0; i < ln; i++) {
+                res.set(i, ((KVP) res.get(i)).value);
+            }
+
+            return new TemplateModelListSequence(res);
+        }
+
+        static Object[] startErrorMessage(int keyNamesLn) {
+            return new Object[] { (keyNamesLn == 0 ? "?sort" : "?sort_by(...)"), " failed: " };
+        }
+        
+        static Object[] startErrorMessage(int keyNamesLn, int index) {
+            return new Object[] {
+                    (keyNamesLn == 0 ? "?sort" : "?sort_by(...)"),
+                    " failed at sequence index ", Integer.valueOf(index),
+                    (index == 0 ? ": " : " (0-based): ") };
+        }
+        
+        static final int KEY_TYPE_NOT_YET_DETECTED = 0;
+
+        static final int KEY_TYPE_STRING = 1;
+
+        static final int KEY_TYPE_NUMBER = 2;
+
+        static final int KEY_TYPE_DATE = 3;
+        
+        static final int KEY_TYPE_BOOLEAN = 4;
+        
+        @Override
+        TemplateModel calculateResult(TemplateSequenceModel seq)
+                throws TemplateModelException {
+            return sort(seq, null);
+        }
+        
+    }
+
+    private static boolean isBuggySeqButGoodCollection(
+            TemplateModel model) {
+        return model instanceof CollectionModel
+                ? !((CollectionModel) model).getSupportsIndexedAccess()
+                : false;
+    }
+    
+    private static boolean modelsEqual(
+            int seqItemIndex, TemplateModel seqItem, TemplateModel searchedItem,
+            Environment env)
+            throws TemplateModelException {
+        try {
+            return EvalUtil.compare(
+                    seqItem, null,
+                    EvalUtil.CMP_OP_EQUALS, null,
+                    searchedItem, null,
+                    null, false,
+                    true, true, true, // The last one is true to emulate an old bug for BC 
+                    env);
+        } catch (TemplateException ex) {
+            throw new _TemplateModelException(ex,
+                    "This error has occurred when comparing sequence item at 0-based index ", Integer.valueOf(seqItemIndex),
+                    " to the searched item:\n", new _DelayedGetMessage(ex));
+        }
+    }
+ 
+    // Can't be instantiated
+    private BuiltInsForSequences() { }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java
new file mode 100644
index 0000000..0bb09bf
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java
@@ -0,0 +1,698 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.model.impl.SimpleSequence;
+import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
+import org.apache.freemarker.core.util._StringUtil;
+
+class BuiltInsForStringsBasic {
+
+    static class cap_firstBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            int i = 0;
+            int ln = s.length();
+            while (i < ln  &&  Character.isWhitespace(s.charAt(i))) {
+                i++;
+            }
+            if (i < ln) {
+                StringBuilder b = new StringBuilder(s);
+                b.setCharAt(i, Character.toUpperCase(s.charAt(i)));
+                s = b.toString();
+            }
+            return new SimpleScalar(s);
+        }
+    }
+
+    static class capitalizeBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.capitalize(s));
+        }
+    }
+
+    static class chop_linebreakBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.chomp(s));
+        }
+    }
+
+    static class containsBI extends ASTExpBuiltIn {
+        
+        private class BIMethod implements TemplateMethodModelEx {
+            
+            private final String s;
+    
+            private BIMethod(String s) {
+                this.s = s;
+            }
+    
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                checkMethodArgCount(args, 1);
+                return s.indexOf(getStringMethodArg(args, 0)) != -1
+                        ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+            }
+        }
+    
+        @Override
+        TemplateModel _eval(Environment env) throws TemplateException {
+            return new BIMethod(target.evalAndCoerceToStringOrUnsupportedMarkup(env,
+                    "For sequences/collections (lists and such) use \"?seq_contains\" instead."));
+        }
+    }
+
+    static class ends_withBI extends BuiltInForString {
+    
+        private class BIMethod implements TemplateMethodModelEx {
+            private String s;
+    
+            private BIMethod(String s) {
+                this.s = s;
+            }
+    
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                checkMethodArgCount(args, 1);
+                return s.endsWith(getStringMethodArg(args, 0)) ?
+                        TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+            }
+        }
+    
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            return new BIMethod(s);
+        }
+    }
+
+    static class ensure_ends_withBI extends BuiltInForString {
+        
+        private class BIMethod implements TemplateMethodModelEx {
+            private String s;
+    
+            private BIMethod(String s) {
+                this.s = s;
+            }
+    
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                checkMethodArgCount(args, 1);
+                String suffix = getStringMethodArg(args, 0);
+                return new SimpleScalar(s.endsWith(suffix) ? s : s + suffix);
+            }
+        }
+    
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            return new BIMethod(s);
+        }
+    }
+
+    static class ensure_starts_withBI extends BuiltInForString {
+        
+        private class BIMethod implements TemplateMethodModelEx {
+            private String s;
+    
+            private BIMethod(String s) {
+                this.s = s;
+            }
+    
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                checkMethodArgCount(args, 1, 3);
+                
+                final String checkedPrefix = getStringMethodArg(args, 0);
+                
+                final boolean startsWithPrefix;
+                final String addedPrefix; 
+                if (args.size() > 1) {
+                    addedPrefix = getStringMethodArg(args, 1);
+                    long flags = args.size() > 2
+                            ? RegexpHelper.parseFlagString(getStringMethodArg(args, 2))
+                            : RegexpHelper.RE_FLAG_REGEXP;
+                  
+                    if ((flags & RegexpHelper.RE_FLAG_REGEXP) == 0) {
+                        RegexpHelper.checkOnlyHasNonRegexpFlags(key, flags, true);
+                        if ((flags & RegexpHelper.RE_FLAG_CASE_INSENSITIVE) == 0) {
+                            startsWithPrefix = s.startsWith(checkedPrefix);
+                        } else {
+                            startsWithPrefix = s.toLowerCase().startsWith(checkedPrefix.toLowerCase());
+                        }
+                    } else {
+                        Pattern pattern = RegexpHelper.getPattern(checkedPrefix, (int) flags);
+                        final Matcher matcher = pattern.matcher(s);
+                        startsWithPrefix = matcher.lookingAt();
+                    } 
+                } else {
+                    startsWithPrefix = s.startsWith(checkedPrefix);
+                    addedPrefix = checkedPrefix;
+                }
+                return new SimpleScalar(startsWithPrefix ? s : addedPrefix + s);
+            }
+        }
+    
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            return new BIMethod(s);
+        }
+    }
+
+    static class index_ofBI extends ASTExpBuiltIn {
+        
+        private class BIMethod implements TemplateMethodModelEx {
+            
+            private final String s;
+            
+            private BIMethod(String s) {
+                this.s = s;
+            }
+            
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                int argCnt = args.size();
+                checkMethodArgCount(argCnt, 1, 2);
+                String subStr = getStringMethodArg(args, 0);
+                if (argCnt > 1) {
+                    int startIdx = getNumberMethodArg(args, 1).intValue();
+                    return new SimpleNumber(findLast ? s.lastIndexOf(subStr, startIdx) : s.indexOf(subStr, startIdx));
+                } else {
+                    return new SimpleNumber(findLast ? s.lastIndexOf(subStr) : s.indexOf(subStr));
+                }
+            }
+        }
+        
+        private final boolean findLast;
+    
+        index_ofBI(boolean findLast) {
+            this.findLast = findLast;
+        }
+        
+        @Override
+        TemplateModel _eval(Environment env) throws TemplateException {
+            return new BIMethod(target.evalAndCoerceToStringOrUnsupportedMarkup(env,
+                    "For sequences/collections (lists and such) use \"?seq_index_of\" instead."));
+        }
+    }
+    
+    static class keep_afterBI extends BuiltInForString {
+        class KeepAfterMethod implements TemplateMethodModelEx {
+            private String s;
+
+            KeepAfterMethod(String s) {
+                this.s = s;
+            }
+
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                int argCnt = args.size();
+                checkMethodArgCount(argCnt, 1, 2);
+                String separatorString = getStringMethodArg(args, 0);
+                long flags = argCnt > 1 ? RegexpHelper.parseFlagString(getStringMethodArg(args, 1)) : 0;
+                
+                int startIndex;
+                if ((flags & RegexpHelper.RE_FLAG_REGEXP) == 0) {
+                    RegexpHelper.checkOnlyHasNonRegexpFlags(key, flags, true);
+                    if ((flags & RegexpHelper.RE_FLAG_CASE_INSENSITIVE) == 0) {
+                        startIndex = s.indexOf(separatorString);
+                    } else {
+                        startIndex = s.toLowerCase().indexOf(separatorString.toLowerCase());
+                    }
+                    if (startIndex >= 0) {
+                        startIndex += separatorString.length();
+                    }
+                } else {
+                    Pattern pattern = RegexpHelper.getPattern(separatorString, (int) flags);
+                    final Matcher matcher = pattern.matcher(s);
+                    if (matcher.find()) {
+                        startIndex = matcher.end();
+                    } else {
+                        startIndex = -1;
+                    }
+                } 
+                return startIndex == -1 ? TemplateScalarModel.EMPTY_STRING : new SimpleScalar(s.substring(startIndex));
+            }
+        }
+        
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateModelException {
+            return new KeepAfterMethod(s);
+        }
+        
+    }
+    
+    static class keep_after_lastBI extends BuiltInForString {
+        class KeepAfterMethod implements TemplateMethodModelEx {
+            private String s;
+
+            KeepAfterMethod(String s) {
+                this.s = s;
+            }
+
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                int argCnt = args.size();
+                checkMethodArgCount(argCnt, 1, 2);
+                String separatorString = getStringMethodArg(args, 0);
+                long flags = argCnt > 1 ? RegexpHelper.parseFlagString(getStringMethodArg(args, 1)) : 0;
+                
+                int startIndex;
+                if ((flags & RegexpHelper.RE_FLAG_REGEXP) == 0) {
+                    RegexpHelper.checkOnlyHasNonRegexpFlags(key, flags, true);
+                    if ((flags & RegexpHelper.RE_FLAG_CASE_INSENSITIVE) == 0) {
+                        startIndex = s.lastIndexOf(separatorString);
+                    } else {
+                        startIndex = s.toLowerCase().lastIndexOf(separatorString.toLowerCase());
+                    }
+                    if (startIndex >= 0) {
+                        startIndex += separatorString.length();
+                    }
+                } else {
+                    if (separatorString.length() == 0) {
+                        startIndex = s.length();
+                    } else {
+                        Pattern pattern = RegexpHelper.getPattern(separatorString, (int) flags);
+                        final Matcher matcher = pattern.matcher(s);
+                        if (matcher.find()) {
+                            startIndex = matcher.end();
+                            while (matcher.find(matcher.start() + 1)) {
+                                startIndex = matcher.end();
+                            }
+                        } else {
+                            startIndex = -1;
+                        }
+                    }
+                } 
+                return startIndex == -1 ? TemplateScalarModel.EMPTY_STRING : new SimpleScalar(s.substring(startIndex));
+            }
+        }
+        
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateModelException {
+            return new KeepAfterMethod(s);
+        }
+        
+    }
+    
+    static class keep_beforeBI extends BuiltInForString {
+        class KeepUntilMethod implements TemplateMethodModelEx {
+            private String s;
+
+            KeepUntilMethod(String s) {
+                this.s = s;
+            }
+
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                int argCnt = args.size();
+                checkMethodArgCount(argCnt, 1, 2);
+                String separatorString = getStringMethodArg(args, 0);
+                long flags = argCnt > 1 ? RegexpHelper.parseFlagString(getStringMethodArg(args, 1)) : 0;
+                
+                int stopIndex;
+                if ((flags & RegexpHelper.RE_FLAG_REGEXP) == 0) {
+                    RegexpHelper.checkOnlyHasNonRegexpFlags(key, flags, true);
+                    if ((flags & RegexpHelper.RE_FLAG_CASE_INSENSITIVE) == 0) {
+                        stopIndex = s.indexOf(separatorString);
+                    } else {
+                        stopIndex = s.toLowerCase().indexOf(separatorString.toLowerCase());
+                    }
+                } else {
+                    Pattern pattern = RegexpHelper.getPattern(separatorString, (int) flags);
+                    final Matcher matcher = pattern.matcher(s);
+                    if (matcher.find()) {
+                        stopIndex = matcher.start();
+                    } else {
+                        stopIndex = -1;
+                    }
+                } 
+                return stopIndex == -1 ? new SimpleScalar(s) : new SimpleScalar(s.substring(0, stopIndex));
+            }
+        }
+        
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateModelException {
+            return new KeepUntilMethod(s);
+        }
+        
+    }
+    
+    // TODO
+    static class keep_before_lastBI extends BuiltInForString {
+        class KeepUntilMethod implements TemplateMethodModelEx {
+            private String s;
+
+            KeepUntilMethod(String s) {
+                this.s = s;
+            }
+
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                int argCnt = args.size();
+                checkMethodArgCount(argCnt, 1, 2);
+                String separatorString = getStringMethodArg(args, 0);
+                long flags = argCnt > 1 ? RegexpHelper.parseFlagString(getStringMethodArg(args, 1)) : 0;
+                
+                int stopIndex;
+                if ((flags & RegexpHelper.RE_FLAG_REGEXP) == 0) {
+                    RegexpHelper.checkOnlyHasNonRegexpFlags(key, flags, true);
+                    if ((flags & RegexpHelper.RE_FLAG_CASE_INSENSITIVE) == 0) {
+                        stopIndex = s.lastIndexOf(separatorString);
+                    } else {
+                        stopIndex = s.toLowerCase().lastIndexOf(separatorString.toLowerCase());
+                    }
+                } else {
+                    if (separatorString.length() == 0) {
+                        stopIndex = s.length();
+                    } else {
+                        Pattern pattern = RegexpHelper.getPattern(separatorString, (int) flags);
+                        final Matcher matcher = pattern.matcher(s);
+                        if (matcher.find()) {
+                            stopIndex = matcher.start();
+                            while (matcher.find(stopIndex + 1)) {
+                                stopIndex = matcher.start();
+                            }
+                        } else {
+                            stopIndex = -1;
+                        }
+                    }
+                } 
+                return stopIndex == -1 ? new SimpleScalar(s) : new SimpleScalar(s.substring(0, stopIndex));
+            }
+        }
+        
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateModelException {
+            return new KeepUntilMethod(s);
+        }
+        
+    }
+    
+    static class lengthBI extends BuiltInForString {
+    
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            return new SimpleNumber(s.length());
+        }
+        
+    }    
+
+    static class lower_caseBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(s.toLowerCase(env.getLocale()));
+        }
+    }    
+
+    static class padBI extends BuiltInForString {
+        
+        private class BIMethod implements TemplateMethodModelEx {
+            
+            private final String s;
+    
+            private BIMethod(String s) {
+                this.s = s;
+            }
+    
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                int argCnt  = args.size();
+                checkMethodArgCount(argCnt, 1, 2);
+    
+                int width = getNumberMethodArg(args, 0).intValue();
+    
+                if (argCnt > 1) {
+                    String filling = getStringMethodArg(args, 1);
+                    try {
+                        return new SimpleScalar(
+                                leftPadder
+                                        ? _StringUtil.leftPad(s, width, filling)
+                                        : _StringUtil.rightPad(s, width, filling));
+                    } catch (IllegalArgumentException e) {
+                        if (filling.length() == 0) {
+                            throw new _TemplateModelException(
+                                    "?", key, "(...) argument #2 can't be a 0-length string.");
+                        } else {
+                            throw new _TemplateModelException(e,
+                                    "?", key, "(...) failed: ", e);
+                        }
+                    }
+                } else {
+                    return new SimpleScalar(leftPadder ? _StringUtil.leftPad(s, width) : _StringUtil.rightPad(s, width));
+                }
+            }
+        }
+    
+        private final boolean leftPadder;
+    
+        padBI(boolean leftPadder) {
+            this.leftPadder = leftPadder;
+        }
+    
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            return new BIMethod(s);
+        }
+    }
+    
+    static class remove_beginningBI extends BuiltInForString {
+        
+        private class BIMethod implements TemplateMethodModelEx {
+            private String s;
+    
+            private BIMethod(String s) {
+                this.s = s;
+            }
+    
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                checkMethodArgCount(args, 1);
+                String prefix = getStringMethodArg(args, 0);
+                return new SimpleScalar(s.startsWith(prefix) ? s.substring(prefix.length()) : s);
+            }
+        }
+    
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            return new BIMethod(s);
+        }
+    }
+
+    static class remove_endingBI extends BuiltInForString {
+    
+        private class BIMethod implements TemplateMethodModelEx {
+            private String s;
+    
+            private BIMethod(String s) {
+                this.s = s;
+            }
+    
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                checkMethodArgCount(args, 1);
+                String suffix = getStringMethodArg(args, 0);
+                return new SimpleScalar(s.endsWith(suffix) ? s.substring(0, s.length() - suffix.length()) : s);
+            }
+        }
+    
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            return new BIMethod(s);
+        }
+    }
+    
+    static class split_BI extends BuiltInForString {
+        class SplitMethod implements TemplateMethodModel {
+            private String s;
+
+            SplitMethod(String s) {
+                this.s = s;
+            }
+
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                int argCnt = args.size();
+                checkMethodArgCount(argCnt, 1, 2);
+                String splitString = (String) args.get(0);
+                long flags = argCnt > 1 ? RegexpHelper.parseFlagString((String) args.get(1)) : 0;
+                String[] result = null;
+                if ((flags & RegexpHelper.RE_FLAG_REGEXP) == 0) {
+                    RegexpHelper.checkNonRegexpFlags("split", flags);
+                    result = _StringUtil.split(s, splitString,
+                            (flags & RegexpHelper.RE_FLAG_CASE_INSENSITIVE) != 0);
+                } else {
+                    Pattern pattern = RegexpHelper.getPattern(splitString, (int) flags);
+                    result = pattern.split(s);
+                } 
+                return _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER.wrap(result);
+            }
+        }
+        
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateModelException {
+            return new SplitMethod(s);
+        }
+        
+    }
+    
+    static class starts_withBI extends BuiltInForString {
+    
+        private class BIMethod implements TemplateMethodModelEx {
+            private String s;
+    
+            private BIMethod(String s) {
+                this.s = s;
+            }
+    
+            @Override
+            public Object exec(List args) throws TemplateModelException {
+                checkMethodArgCount(args, 1);
+                return s.startsWith(getStringMethodArg(args, 0)) ?
+                        TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+            }
+        }
+    
+        @Override
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            return new BIMethod(s);
+        }
+    }
+
+    static class substringBI extends BuiltInForString {
+        
+        @Override
+        TemplateModel calculateResult(final String s, final Environment env) throws TemplateException {
+            return new TemplateMethodModelEx() {
+                
+                @Override
+                public Object exec(java.util.List args) throws TemplateModelException {
+                    int argCount = args.size();
+                    checkMethodArgCount(argCount, 1, 2);
+    
+                    int beginIdx = getNumberMethodArg(args, 0).intValue();
+    
+                    final int len = s.length();
+    
+                    if (beginIdx < 0) {
+                        throw newIndexLessThan0Exception(0, beginIdx);
+                    } else if (beginIdx > len) {
+                        throw newIndexGreaterThanLengthException(0, beginIdx, len);
+                    }
+    
+                    if (argCount > 1) {
+                        int endIdx = getNumberMethodArg(args, 1).intValue();
+                        if (endIdx < 0) {
+                            throw newIndexLessThan0Exception(1, endIdx);
+                        } else if (endIdx > len) {
+                            throw newIndexGreaterThanLengthException(1, endIdx, len);
+                        }
+                        if (beginIdx > endIdx) {
+                            throw MessageUtil.newMethodArgsInvalidValueException("?" + key,
+                                    "The begin index argument, ", Integer.valueOf(beginIdx),
+                                    ", shouldn't be greater than the end index argument, ",
+                                    Integer.valueOf(endIdx), ".");
+                        }
+                        return new SimpleScalar(s.substring(beginIdx, endIdx));
+                    } else {
+                        return new SimpleScalar(s.substring(beginIdx));
+                    }
+                }
+    
+                private TemplateModelException newIndexGreaterThanLengthException(
+                        int argIdx, int idx, final int len) throws TemplateModelException {
+                    return MessageUtil.newMethodArgInvalidValueException(
+                            "?" + key, argIdx,
+                            "The index mustn't be greater than the length of the string, ",
+                            Integer.valueOf(len),
+                            ", but it was ", Integer.valueOf(idx), ".");
+                }
+    
+                private TemplateModelException newIndexLessThan0Exception(
+                        int argIdx, int idx) throws TemplateModelException {
+                    return MessageUtil.newMethodArgInvalidValueException(
+                            "?" + key, argIdx,
+                            "The index must be at least 0, but was ", Integer.valueOf(idx), ".");
+                }
+                
+            };
+        }
+    }
+
+    static class trimBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(s.trim());
+        }
+    }
+
+    static class uncap_firstBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            int i = 0;
+            int ln = s.length();
+            while (i < ln  &&  Character.isWhitespace(s.charAt(i))) {
+                i++;
+            }
+            if (i < ln) {
+                StringBuilder b = new StringBuilder(s);
+                b.setCharAt(i, Character.toLowerCase(s.charAt(i)));
+                s = b.toString();
+            }
+            return new SimpleScalar(s);
+        }
+    }
+
+    static class upper_caseBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(s.toUpperCase(env.getLocale()));
+        }
+    }
+
+    static class word_listBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            SimpleSequence result = new SimpleSequence();
+            StringTokenizer st = new StringTokenizer(s);
+            while (st.hasMoreTokens()) {
+               result.add(st.nextToken());
+            }
+            return result;
+        }
+    }
+
+    // Can't be instantiated
+    private BuiltInsForStringsBasic() { }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsEncoding.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsEncoding.java b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsEncoding.java
new file mode 100644
index 0000000..28dd510
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsEncoding.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.util._StringUtil;
+
+class BuiltInsForStringsEncoding {
+
+    static class htmlBI extends BuiltInForLegacyEscaping {
+        
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.XHTMLEnc(s));
+        }
+        
+    }
+
+    static class j_stringBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.javaStringEnc(s));
+        }
+    }
+
+    static class js_stringBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.javaScriptStringEnc(s));
+        }
+    }
+
+    static class json_stringBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.jsonStringEnc(s));
+        }
+    }
+
+    static class rtfBI extends BuiltInForLegacyEscaping {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.RTFEnc(s));
+        }
+    }
+
+    static class urlBI extends BuiltInForString {
+        
+        static class UrlBIResult extends BuiltInsForStringsEncoding.AbstractUrlBIResult {
+    
+            protected UrlBIResult(ASTExpBuiltIn parent, String target, Environment env) {
+                super(parent, target, env);
+            }
+    
+            @Override
+            protected String encodeWithCharset(String cs) throws UnsupportedEncodingException {
+                return _StringUtil.URLEnc(targetAsString, cs);
+            }
+            
+        }
+        
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new UrlBIResult(this, s, env);
+        }
+        
+    }
+
+    static class urlPathBI extends BuiltInForString {
+    
+        static class UrlPathBIResult extends BuiltInsForStringsEncoding.AbstractUrlBIResult {
+    
+            protected UrlPathBIResult(ASTExpBuiltIn parent, String target, Environment env) {
+                super(parent, target, env);
+            }
+    
+            @Override
+            protected String encodeWithCharset(String cs) throws UnsupportedEncodingException {
+                return _StringUtil.URLPathEnc(targetAsString, cs);
+            }
+            
+        }
+        
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new UrlPathBIResult(this, s, env);
+        }
+        
+    }
+
+    static class xhtmlBI extends BuiltInForLegacyEscaping {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.XHTMLEnc(s));
+        }
+    }
+
+    static class xmlBI extends BuiltInForLegacyEscaping {
+        @Override
+        TemplateModel calculateResult(String s, Environment env) {
+            return new SimpleScalar(_StringUtil.XMLEnc(s));
+        }
+    }
+
+    // Can't be instantiated
+    private BuiltInsForStringsEncoding() { }
+
+    static abstract class AbstractUrlBIResult implements
+    TemplateScalarModel, TemplateMethodModel {
+        
+        protected final ASTExpBuiltIn parent;
+        protected final String targetAsString;
+        private final Environment env;
+        private String cachedResult;
+        
+        protected AbstractUrlBIResult(ASTExpBuiltIn parent, String targetAsString, Environment env) {
+            this.parent = parent;
+            this.targetAsString = targetAsString;
+            this.env = env;
+        }
+        
+        protected abstract String encodeWithCharset(String cs) throws UnsupportedEncodingException;
+    
+        @Override
+        public Object exec(List args) throws TemplateModelException {
+            parent.checkMethodArgCount(args.size(), 1);
+            try {
+                return new SimpleScalar(encodeWithCharset((String) args.get(0)));
+            } catch (UnsupportedEncodingException e) {
+                throw new _TemplateModelException(e, "Failed to execute URL encoding.");
+            }
+        }
+        
+        @Override
+        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 = encodeWithCharset(cs);
+                } catch (UnsupportedEncodingException e) {
+                    throw new _TemplateModelException(e, "Failed to execute URL encoding.");
+                }
+            }
+            return cachedResult;
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
new file mode 100644
index 0000000..543143a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -0,0 +1,301 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.TemplateTransformModel;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
+import org.apache.freemarker.core.model.impl.beans.BeanModel;
+import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
+
+class BuiltInsForStringsMisc {
+
+    // Can't be instantiated
+    private BuiltInsForStringsMisc() { }
+    
+    static class booleanBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env)  throws TemplateException {
+            final boolean b;
+            if (s.equals("true")) {
+                b = true;
+            } else if (s.equals("false")) {
+                b = false;
+            } else if (s.equals(env.getTrueStringValue())) {
+                b = true;
+            } else if (s.equals(env.getFalseStringValue())) {
+                b = false;
+            } else {
+                throw new _MiscTemplateException(this, env,
+                        "Can't convert this string to boolean: ", new _DelayedJQuote(s));
+            }
+            return b ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+        }
+    }
+
+    static class evalBI extends OutputFormatBoundBuiltIn {
+        
+        @Override
+        protected TemplateModel calculateResult(Environment env) throws TemplateException {
+            return calculateResult(BuiltInForString.getTargetString(target, env), env);
+        }
+        
+        TemplateModel calculateResult(String s, Environment env) throws TemplateException {
+            Template parentTemplate = getTemplate();
+            
+            ASTExpression exp = null;
+            try {
+                try {
+                    ParserConfiguration pCfg = parentTemplate.getParserConfiguration();
+                    
+                    SimpleCharStream simpleCharStream = new SimpleCharStream(
+                            new StringReader("(" + s + ")"),
+                            RUNTIME_EVAL_LINE_DISPLACEMENT, 1,
+                            s.length() + 2);
+                    simpleCharStream.setTabSize(pCfg.getTabSize());
+                    FMParserTokenManager tkMan = new FMParserTokenManager(
+                            simpleCharStream);
+                    tkMan.SwitchTo(FMParserConstants.FM_EXPRESSION);
+
+                    // pCfg.outputFormat is exceptional: it's inherited from the lexical context
+                    if (pCfg.getOutputFormat() != outputFormat) {
+                        pCfg = new _ParserConfigurationWithInheritedFormat(
+                                pCfg, outputFormat, Integer.valueOf(autoEscapingPolicy));
+                    }
+                    
+                    FMParser parser = new FMParser(
+                            parentTemplate, false, tkMan, pCfg, TemplateSpecifiedEncodingHandler.DEFAULT);
+                    
+                    exp = parser.ASTExpression();
+                } catch (TokenMgrError e) {
+                    throw e.toParseException(parentTemplate);
+                }
+            } catch (ParseException e) {
+                throw new _MiscTemplateException(this, env,
+                        "Failed to \"?", key, "\" string with this error:\n\n",
+                        MessageUtil.EMBEDDED_MESSAGE_BEGIN,
+                        new _DelayedGetMessage(e),
+                        MessageUtil.EMBEDDED_MESSAGE_END,
+                        "\n\nThe failing expression:");
+            }
+            try {
+                return exp.eval(env);
+            } catch (TemplateException e) {
+                throw new _MiscTemplateException(this, env,
+                        "Failed to \"?", key, "\" string with this error:\n\n",
+                        MessageUtil.EMBEDDED_MESSAGE_BEGIN,
+                        new _DelayedGetMessageWithoutStackTop(e),
+                        MessageUtil.EMBEDDED_MESSAGE_END,
+                        "\n\nThe failing expression:");
+            }
+        }
+        
+    }
+    
+    /**
+     * A method that takes a parameter and evaluates it as a scalar,
+     * then treats that scalar as template source code and returns a
+     * transform model that evaluates the template in place.
+     * The template inherits the configuration and environment of the executing
+     * template. By default, its name will be equal to 
+     * <tt>executingTemplate.getName() + "$anonymous_interpreted"</tt>. You can
+     * specify another parameter to the method call in which case the
+     * template name suffix is the specified id instead of "anonymous_interpreted".
+     */
+    static class interpretBI extends OutputFormatBoundBuiltIn {
+        
+        /**
+         * Constructs a template on-the-fly and returns it embedded in a
+         * {@link TemplateTransformModel}.
+         * 
+         * <p>The built-in has two arguments:
+         * the arguments passed to the method. It can receive at
+         * least one and at most two arguments, both must evaluate to a scalar. 
+         * The first scalar is interpreted as a template source code and a template
+         * is built from it. The second (optional) is used to give the generated
+         * template a name.
+         * 
+         * @return a {@link TemplateTransformModel} that when executed inside
+         * a <tt>&lt;transform></tt> block will process the generated template
+         * just as if it had been <tt>&lt;transform></tt>-ed at that point.
+         */
+        @Override
+        protected TemplateModel calculateResult(Environment env) throws TemplateException {
+            TemplateModel model = target.eval(env);
+            ASTExpression sourceExpr = null;
+            String id = "anonymous_interpreted";
+            if (model instanceof TemplateSequenceModel) {
+                sourceExpr = ((ASTExpression) new ASTExpDynamicKeyName(target, new ASTExpNumberLiteral(Integer.valueOf(0))).copyLocationFrom(target));
+                if (((TemplateSequenceModel) model).size() > 1) {
+                    id = ((ASTExpression) new ASTExpDynamicKeyName(target, new ASTExpNumberLiteral(Integer.valueOf(1))).copyLocationFrom(target)).evalAndCoerceToPlainText(env);
+                }
+            } else if (model instanceof TemplateScalarModel) {
+                sourceExpr = target;
+            } else {
+                throw new UnexpectedTypeException(
+                        target, model,
+                        "sequence or string", new Class[] { TemplateSequenceModel.class, TemplateScalarModel.class },
+                        env);
+            }
+            String templateSource = sourceExpr.evalAndCoerceToPlainText(env);
+            Template parentTemplate = env.getCurrentTemplate();
+            
+            final Template interpretedTemplate;
+            try {
+                ParserConfiguration pCfg = parentTemplate.getParserConfiguration();
+                // pCfg.outputFormat is exceptional: it's inherited from the lexical context
+                if (pCfg.getOutputFormat() != outputFormat) {
+                    pCfg = new _ParserConfigurationWithInheritedFormat(
+                            pCfg, outputFormat, Integer.valueOf(autoEscapingPolicy));
+                }
+                interpretedTemplate = new Template(
+                        (parentTemplate.getName() != null ? parentTemplate.getName() : "nameless_template") + "->" + id,
+                        null,
+                        new StringReader(templateSource),
+                        parentTemplate.getConfiguration(), pCfg,
+                        null);
+            } catch (IOException e) {
+                throw new _MiscTemplateException(this, e, env, new Object[] {
+                            "Template parsing with \"?", key, "\" has failed with this error:\n\n",
+                            MessageUtil.EMBEDDED_MESSAGE_BEGIN,
+                            new _DelayedGetMessage(e),
+                            MessageUtil.EMBEDDED_MESSAGE_END,
+                            "\n\nThe failed expression:" });
+            }
+            
+            interpretedTemplate.setLocale(env.getLocale());
+            return new TemplateProcessorModel(interpretedTemplate);
+        }
+
+        private class TemplateProcessorModel
+        implements
+            TemplateTransformModel {
+            private final Template template;
+            
+            TemplateProcessorModel(Template template) {
+                this.template = template;
+            }
+            
+            @Override
+            public Writer getWriter(final Writer out, Map args) throws TemplateModelException, IOException {
+                try {
+                    Environment env = Environment.getCurrentEnvironment();
+                    boolean lastFIRE = env.setFastInvalidReferenceExceptions(false);
+                    try {
+                        env.include(template);
+                    } finally {
+                        env.setFastInvalidReferenceExceptions(lastFIRE);
+                    }
+                } catch (Exception e) {
+                    throw new _TemplateModelException(e,
+                            "Template created with \"?", key, "\" has stopped with this error:\n\n",
+                            MessageUtil.EMBEDDED_MESSAGE_BEGIN,
+                            new _DelayedGetMessage(e),
+                            MessageUtil.EMBEDDED_MESSAGE_END);
+                }
+        
+                return new Writer(out)
+                {
+                    @Override
+                    public void close() {
+                    }
+                    
+                    @Override
+                    public void flush() throws IOException {
+                        out.flush();
+                    }
+                    
+                    @Override
+                    public void write(char[] cbuf, int off, int len) throws IOException {
+                        out.write(cbuf, off, len);
+                    }
+                };
+            }
+        }
+
+    }
+
+    static class numberBI extends BuiltInForString {
+        @Override
+        TemplateModel calculateResult(String s, Environment env)  throws TemplateException {
+            try {
+                return new SimpleNumber(env.getArithmeticEngine().toNumber(s));
+            } catch (NumberFormatException nfe) {
+                throw NonNumericalException.newMalformedNumberException(this, s, env);
+            }
+        }
+    }
+
+    /**
+     * A built-in that allows us to instantiate an instance of a java class.
+     * Usage is something like: <tt>&lt;#assign foobar = "foo.bar.MyClass"?new()></tt>;
+     */
+    static class newBI extends ASTExpBuiltIn {
+        
+        @Override
+        TemplateModel _eval(Environment env)
+                throws TemplateException {
+            return new ConstructorFunction(target.evalAndCoerceToPlainText(env), env, target.getTemplate());
+        }
+
+        class ConstructorFunction implements TemplateMethodModelEx {
+
+            private final Class<?> cl;
+            private final Environment env;
+            
+            public ConstructorFunction(String classname, Environment env, Template template) throws TemplateException {
+                this.env = env;
+                cl = env.getNewBuiltinClassResolver().resolve(classname, env, template);
+                if (!TemplateModel.class.isAssignableFrom(cl)) {
+                    throw new _MiscTemplateException(newBI.this, env,
+                            "Class ", cl.getName(), " does not implement org.apache.freemarker.core.TemplateModel");
+                }
+                if (BeanModel.class.isAssignableFrom(cl)) {
+                    throw new _MiscTemplateException(newBI.this, env,
+                            "Bean Models cannot be instantiated using the ?", key, " built-in");
+                }
+            }
+
+            @Override
+            public Object exec(List arguments) throws TemplateModelException {
+                ObjectWrapper ow = env.getObjectWrapper();
+                BeansWrapper bw = 
+                    ow instanceof BeansWrapper 
+                    ? (BeansWrapper) ow
+                    : _StaticObjectWrappers.BEANS_WRAPPER;
+                return bw.newInstance(cl, arguments);
+            }
+        }
+    }
+    
+}


Mime
View raw message