freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [38/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:05 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/JavaTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/JavaTemplateDateFormat.java b/src/main/java/org/apache/freemarker/core/JavaTemplateDateFormat.java
new file mode 100644
index 0000000..949cc9e
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/JavaTemplateDateFormat.java
@@ -0,0 +1,72 @@
+/*
+ * 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.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Java {@link DateFormat}-based format.
+ */
+class JavaTemplateDateFormat extends TemplateDateFormat {
+    
+    private final DateFormat javaDateFormat;
+
+    public JavaTemplateDateFormat(DateFormat javaDateFormat) {
+        this.javaDateFormat = javaDateFormat;
+    }
+    
+    @Override
+    public String formatToPlainText(TemplateDateModel dateModel) throws TemplateModelException {
+        return javaDateFormat.format(TemplateFormatUtil.getNonNullDate(dateModel));
+    }
+
+    @Override
+    public Date parse(String s, int dateType) throws UnparsableValueException {
+        try {
+            return javaDateFormat.parse(s);
+        } catch (ParseException e) {
+            throw new UnparsableValueException(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public String getDescription() {
+        return javaDateFormat instanceof SimpleDateFormat
+                ? ((SimpleDateFormat) javaDateFormat).toPattern()
+                : javaDateFormat.toString();
+    }
+
+    @Override
+    public boolean isLocaleBound() {
+        return true;
+    }
+
+    @Override
+    public boolean isTimeZoneBound() {
+        return true;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/JavaTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/JavaTemplateDateFormatFactory.java b/src/main/java/org/apache/freemarker/core/JavaTemplateDateFormatFactory.java
new file mode 100644
index 0000000..331a0ca
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/JavaTemplateDateFormatFactory.java
@@ -0,0 +1,173 @@
+/*
+ * 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.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.slf4j.Logger;
+
+class JavaTemplateDateFormatFactory extends TemplateDateFormatFactory {
+    
+    static final JavaTemplateDateFormatFactory INSTANCE = new JavaTemplateDateFormatFactory(); 
+    
+    private static final Logger LOG = _CoreLogs.RUNTIME;
+
+    private static final ConcurrentHashMap<CacheKey, DateFormat> GLOBAL_FORMAT_CACHE
+            = new ConcurrentHashMap<>();
+    private static final int LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE = 1024;
+    
+    private JavaTemplateDateFormatFactory() {
+        // Can't be instantiated
+    }
+    
+    /**
+     * @param zonelessInput
+     *            Has no effect in this implementation.
+     */
+    @Override
+    public TemplateDateFormat get(String params, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput,
+            Environment env) throws UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException {
+        return new JavaTemplateDateFormat(getJavaDateFormat(dateType, params, locale, timeZone));
+    }
+
+    /**
+     * Returns a "private" copy (not in the global cache) for the given format.  
+     */
+    private DateFormat getJavaDateFormat(int dateType, String nameOrPattern, Locale locale, TimeZone timeZone)
+            throws UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException {
+
+        // Get DateFormat from global cache:
+        CacheKey cacheKey = new CacheKey(dateType, nameOrPattern, locale, timeZone);
+        DateFormat jFormat;
+        
+        jFormat = GLOBAL_FORMAT_CACHE.get(cacheKey);
+        if (jFormat == null) {
+            // Add format to global format cache.
+            StringTokenizer tok = new StringTokenizer(nameOrPattern, "_");
+            int tok1Style = tok.hasMoreTokens() ? parseDateStyleToken(tok.nextToken()) : DateFormat.DEFAULT;
+            if (tok1Style != -1) {
+                switch (dateType) {
+                    case TemplateDateModel.UNKNOWN: {
+                        throw new UnknownDateTypeFormattingUnsupportedException();
+                    }
+                    case TemplateDateModel.TIME: {
+                        jFormat = DateFormat.getTimeInstance(tok1Style, cacheKey.locale);
+                        break;
+                    }
+                    case TemplateDateModel.DATE: {
+                        jFormat = DateFormat.getDateInstance(tok1Style, cacheKey.locale);
+                        break;
+                    }
+                    case TemplateDateModel.DATETIME: {
+                        int tok2Style = tok.hasMoreTokens() ? parseDateStyleToken(tok.nextToken()) : tok1Style;
+                        if (tok2Style != -1) {
+                            jFormat = DateFormat.getDateTimeInstance(tok1Style, tok2Style, cacheKey.locale);
+                        }
+                        break;
+                    }
+                }
+            }
+            if (jFormat == null) {
+                try {
+                    jFormat = new SimpleDateFormat(nameOrPattern, cacheKey.locale);
+                } catch (IllegalArgumentException e) {
+                    final String msg = e.getMessage();
+                    throw new InvalidFormatParametersException(
+                            msg != null ? msg : "Invalid SimpleDateFormat pattern", e);
+                }
+            }
+            jFormat.setTimeZone(cacheKey.timeZone);
+            
+            if (GLOBAL_FORMAT_CACHE.size() >= LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE) {
+                boolean triggered = false;
+                synchronized (JavaTemplateNumberFormatFactory.class) {
+                    if (GLOBAL_FORMAT_CACHE.size() >= LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE) {
+                        triggered = true;
+                        GLOBAL_FORMAT_CACHE.clear();
+                    }
+                }
+                if (triggered) {
+                    LOG.warn("Global Java DateFormat cache has exceeded {} entries => cache flushed. "
+                            + "Typical cause: Some template generates high variety of format pattern strings.",
+                            LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE);
+                }
+            }
+            
+            DateFormat prevJFormat = GLOBAL_FORMAT_CACHE.putIfAbsent(cacheKey, jFormat);
+            if (prevJFormat != null) {
+                jFormat = prevJFormat;
+            }
+        }  // if cache miss
+        
+        return (DateFormat) jFormat.clone();  // For thread safety
+    }
+
+    private static final class CacheKey {
+        private final int dateType;
+        private final String pattern;
+        private final Locale locale;
+        private final TimeZone timeZone;
+
+        CacheKey(int dateType, String pattern, Locale locale, TimeZone timeZone) {
+            this.dateType = dateType;
+            this.pattern = pattern;
+            this.locale = locale;
+            this.timeZone = timeZone;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof CacheKey) {
+                CacheKey fk = (CacheKey) o;
+                return dateType == fk.dateType && fk.pattern.equals(pattern) && fk.locale.equals(locale)
+                        && fk.timeZone.equals(timeZone);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return dateType ^ pattern.hashCode() ^ locale.hashCode() ^ timeZone.hashCode();
+        }
+    }
+
+    private int parseDateStyleToken(String token) {
+        if ("short".equals(token)) {
+            return DateFormat.SHORT;
+        }
+        if ("medium".equals(token)) {
+            return DateFormat.MEDIUM;
+        }
+        if ("long".equals(token)) {
+            return DateFormat.LONG;
+        }
+        if ("full".equals(token)) {
+            return DateFormat.FULL;
+        }
+        return -1;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormat.java b/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormat.java
new file mode 100644
index 0000000..9e37c6f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormat.java
@@ -0,0 +1,66 @@
+/*
+ * 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.text.NumberFormat;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+final class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFormat {
+    
+    private final String formatString;
+    private final NumberFormat javaNumberFormat;
+
+    public JavaTemplateNumberFormat(NumberFormat javaNumberFormat, String formatString) {
+        this.formatString = formatString;
+        this.javaNumberFormat = javaNumberFormat;
+    }
+
+    @Override
+    public String formatToPlainText(TemplateNumberModel numberModel) throws UnformattableValueException, TemplateModelException {
+        Number number = TemplateFormatUtil.getNonNullNumber(numberModel);
+        return format(number);
+    }
+
+    @Override
+    public boolean isLocaleBound() {
+        return true;
+    }
+
+    @Override
+    String format(Number number) throws UnformattableValueException {
+        try {
+            return javaNumberFormat.format(number);
+        } catch (ArithmeticException e) {
+            throw new UnformattableValueException(
+                    "This format can't format the " + number + " number. Reason: " + e.getMessage(), e);
+        }
+    }
+
+    public NumberFormat getJavaNumberFormat() {
+        return javaNumberFormat;
+    }
+
+    @Override
+    public String getDescription() {
+        return formatString;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormatFactory.java b/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormatFactory.java
new file mode 100644
index 0000000..e73e2ec
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormatFactory.java
@@ -0,0 +1,120 @@
+/*
+ * 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.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+
+/**
+ * Deals with {@link TemplateNumberFormat}-s that just wrap a Java {@link NumberFormat}.
+ */
+class JavaTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
+    
+    static final JavaTemplateNumberFormatFactory INSTANCE = new JavaTemplateNumberFormatFactory();
+    
+    private static final Logger LOG = _CoreLogs.RUNTIME;
+
+    private static final ConcurrentHashMap<CacheKey, NumberFormat> GLOBAL_FORMAT_CACHE
+            = new ConcurrentHashMap<>();
+    private static final int LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE = 1024;
+    
+    private JavaTemplateNumberFormatFactory() {
+        // Not meant to be instantiated
+    }
+    
+    @Override
+    public TemplateNumberFormat get(String params, Locale locale, Environment env)
+            throws InvalidFormatParametersException {
+        CacheKey cacheKey = new CacheKey(params, locale);
+        NumberFormat jFormat = GLOBAL_FORMAT_CACHE.get(cacheKey);
+        if (jFormat == null) {
+            if ("number".equals(params)) {
+                jFormat = NumberFormat.getNumberInstance(locale);
+            } else if ("currency".equals(params)) {
+                jFormat = NumberFormat.getCurrencyInstance(locale);
+            } else if ("percent".equals(params)) {
+                jFormat = NumberFormat.getPercentInstance(locale);
+            } else if ("computer".equals(params)) {
+                jFormat = env.getCNumberFormat();
+            } else {
+                try {
+                    jFormat = ExtendedDecimalFormatParser.parse(params, locale);
+                } catch (ParseException e) {
+                    String msg = e.getMessage();
+                    throw new InvalidFormatParametersException(
+                            msg != null ? msg : "Invalid DecimalFormat pattern", e);
+                }
+            }
+
+            if (GLOBAL_FORMAT_CACHE.size() >= LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE) {
+                boolean triggered = false;
+                synchronized (JavaTemplateNumberFormatFactory.class) {
+                    if (GLOBAL_FORMAT_CACHE.size() >= LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE) {
+                        triggered = true;
+                        GLOBAL_FORMAT_CACHE.clear();
+                    }
+                }
+                if (triggered) {
+                    LOG.warn("Global Java NumberFormat cache has exceeded {} entries => cache flushed. "
+                            + "Typical cause: Some template generates high variety of format pattern strings.",
+                            LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE);
+                }
+            }
+            
+            NumberFormat prevJFormat = GLOBAL_FORMAT_CACHE.putIfAbsent(cacheKey, jFormat);
+            if (prevJFormat != null) {
+                jFormat = prevJFormat;
+            }
+        }  // if cache miss
+        
+        // JFormat-s aren't thread-safe; must clone it
+        jFormat = (NumberFormat) jFormat.clone();
+        
+        return new JavaTemplateNumberFormat(jFormat, params); 
+    }
+
+    private static final class CacheKey {
+        private final String pattern;
+        private final Locale locale;
+
+        CacheKey(String pattern, Locale locale) {
+            this.pattern = pattern;
+            this.locale = locale;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof CacheKey) {
+                CacheKey fk = (CacheKey) o;
+                return fk.pattern.equals(pattern) && fk.locale.equals(locale);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return pattern.hashCode() ^ locale.hashCode();
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java b/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
new file mode 100644
index 0000000..2130318
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
@@ -0,0 +1,97 @@
+/*
+ * 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.math.BigInteger;
+
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+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.impl.SimpleNumber;
+
+/**
+ * This is the model used for right-unbounded ranges since Incompatible Improvements 2.3.21.
+ * 
+ * @since 2.3.21
+ */
+final class ListableRightUnboundedRangeModel extends RightUnboundedRangeModel implements TemplateCollectionModel {
+
+    ListableRightUnboundedRangeModel(int begin) {
+        super(begin);
+    }
+
+    @Override
+    public int size() throws TemplateModelException {
+        return Integer.MAX_VALUE;
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateModelException {
+        return new TemplateModelIterator() {
+            boolean needInc;
+            int nextType = 1;
+            int nextInt = getBegining();
+            long nextLong;
+            BigInteger nextBigInteger;
+
+            @Override
+            public TemplateModel next() throws TemplateModelException {
+                if (needInc) {
+                    switch (nextType) {
+                    case 1:
+                        if (nextInt < Integer.MAX_VALUE) {
+                            nextInt++;
+                        } else {
+                            nextType = 2;
+                            nextLong = nextInt + 1L;
+                        }
+                        break;
+                        
+                    case 2:
+                        if (nextLong < Long.MAX_VALUE) {
+                            nextLong++;
+                        } else {
+                            nextType = 3;
+                            nextBigInteger = BigInteger.valueOf(nextLong);
+                            nextBigInteger = nextBigInteger.add(BigInteger.ONE);
+                        }
+                        break;
+                        
+                    default: // 3
+                        nextBigInteger = nextBigInteger.add(BigInteger.ONE);
+                    }
+                }
+                needInc = true;
+                return nextType == 1 ? new SimpleNumber(nextInt)
+                        : (nextType == 2 ? new SimpleNumber(nextLong)
+                        : new SimpleNumber(nextBigInteger)); 
+            }
+
+            @Override
+            public boolean hasNext() throws TemplateModelException {
+                return true;
+            }
+            
+        };
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/LocalContext.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/LocalContext.java b/src/main/java/org/apache/freemarker/core/LocalContext.java
new file mode 100644
index 0000000..b8717b7
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/LocalContext.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Collection;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+  * An interface that represents a local context. This is used as the abstraction for  
+  * the context of a ASTDirMacro invocation, a loop, or the nested block call from within 
+  * a macro.
+  */
+public interface LocalContext {
+    TemplateModel getLocalVariable(String name) throws TemplateModelException;
+    Collection getLocalVariableNames() throws TemplateModelException;
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/LocalContextStack.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/LocalContextStack.java b/src/main/java/org/apache/freemarker/core/LocalContextStack.java
new file mode 100644
index 0000000..26aacf3
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/LocalContextStack.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+/**
+ * Class that's a little bit more efficient than using an {@code ArrayList<LocalContext>}. 
+ * 
+ * @since 2.3.24
+ */
+final class LocalContextStack {
+
+    private LocalContext[] buffer = new LocalContext[8];
+    private int size;
+
+    void push(LocalContext localContext) {
+        final int newSize = ++size;
+        LocalContext[] buffer = this.buffer;
+        if (buffer.length < newSize) {
+            final LocalContext[] newBuffer = new LocalContext[newSize * 2];
+            for (int i = 0; i < buffer.length; i++) {
+                newBuffer[i] = buffer[i];
+            }
+            buffer = newBuffer;
+            this.buffer = newBuffer;
+        }
+        buffer[newSize - 1] = localContext;
+    }
+
+    void pop() {
+        buffer[--size] = null;
+    }
+
+    public LocalContext get(int index) {
+        return buffer[index];
+    }
+    
+    public int size() {
+        return size;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/MarkReleaserTemplateSpecifiedEncodingHandler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MarkReleaserTemplateSpecifiedEncodingHandler.java b/src/main/java/org/apache/freemarker/core/MarkReleaserTemplateSpecifiedEncodingHandler.java
new file mode 100644
index 0000000..555bbf3
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MarkReleaserTemplateSpecifiedEncodingHandler.java
@@ -0,0 +1,56 @@
+/*
+ * 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.InputStream;
+
+import org.apache.freemarker.core.Template.WrongEncodingException;
+
+/**
+ * A {@link TemplateSpecifiedEncodingHandler} that discards the mark of the specified {@link InputStream} when
+ * the template parsing gets to a point where it's known that we can't receive a template specified encoding anymore.
+ * This allows freeing up the mark buffer early during parsing.
+ * 
+ * @since 2.3.16
+ */
+public class MarkReleaserTemplateSpecifiedEncodingHandler implements TemplateSpecifiedEncodingHandler {
+
+    private final InputStream markedInputStream;
+
+    /**
+     * @param markedInputStream Input stream with marked template content start position
+     */
+    public MarkReleaserTemplateSpecifiedEncodingHandler(InputStream markedInputStream) {
+        this.markedInputStream = markedInputStream;
+    }
+
+    @Override
+    public void handle(String templateSpecificEncoding, String constructorSpecifiedEncoding)
+            throws WrongEncodingException {
+        TemplateSpecifiedEncodingHandler.DEFAULT.handle(templateSpecificEncoding, constructorSpecifiedEncoding);
+        
+        // There was no WrongEncodingException exception, release mark buffer:
+        markedInputStream.mark(0); // 
+    }
+
+    public InputStream getMarkedInputStream() {
+        return markedInputStream;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/MarkupOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MarkupOutputFormat.java b/src/main/java/org/apache/freemarker/core/MarkupOutputFormat.java
new file mode 100644
index 0000000..5966f6f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MarkupOutputFormat.java
@@ -0,0 +1,131 @@
+/*
+ * 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.Writer;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Superclass of {@link OutputFormat}-s that represent a "markup" format, which is any format where certain character
+ * sequences have special meaning and thus may need escaping. (Escaping is important for FreeMarker, as typically it has
+ * to insert non-markup text from the data-model into the output markup. See also:
+ * {@link Configuration#setOutputFormat(OutputFormat)}.)
+ * 
+ * <p>
+ * An {@link OutputFormat} subclass always has a corresponding {@link TemplateMarkupOutputModel} subclass pair (like
+ * {@link HTMLOutputFormat} has {@link TemplateHTMLOutputModel}). The {@link OutputFormat} implements the operations
+ * related to {@link TemplateMarkupOutputModel} objects of that kind, while the {@link TemplateMarkupOutputModel} only
+ * encapsulates the data (the actual markup or text).
+ * 
+ * <p>
+ * To implement a custom output format, you may want to extend {@link CommonMarkupOutputFormat}.
+ * 
+ * @param <MO>
+ *            The {@link TemplateMarkupOutputModel} class this output format can deal with.
+ * 
+ * @since 2.3.24
+ */
+public abstract class MarkupOutputFormat<MO extends TemplateMarkupOutputModel> extends OutputFormat {
+
+    protected MarkupOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    /**
+     * Converts a {@link String} that's assumed to be plain text to {@link TemplateMarkupOutputModel}, by escaping any
+     * special characters in the plain text. This corresponds to {@code ?esc}, or, to outputting with auto-escaping if
+     * that wasn't using {@link #output(String, Writer)} as an optimization.
+     * 
+     * @see #escapePlainText(String)
+     * @see #getSourcePlainText(TemplateMarkupOutputModel)
+     */
+    public abstract MO fromPlainTextByEscaping(String textToEsc) throws TemplateModelException;
+
+    /**
+     * Wraps a {@link String} that's already markup to {@link TemplateMarkupOutputModel} interface, to indicate its
+     * format. This corresponds to {@code ?noEsc}. (This methods is allowed to throw {@link TemplateModelException} if
+     * the parameter markup text is malformed, but it's unlikely that an implementation chooses to parse the parameter
+     * until, and if ever, that becomes necessary.)
+     * 
+     * @see #getMarkupString(TemplateMarkupOutputModel)
+     */
+    public abstract MO fromMarkup(String markupText) throws TemplateModelException;
+
+    /**
+     * Prints the parameter model to the output.
+     */
+    public abstract void output(MO mo, Writer out) throws IOException, TemplateModelException;
+
+    /**
+     * Equivalent to calling {@link #fromPlainTextByEscaping(String)} and then
+     * {@link #output(TemplateMarkupOutputModel, Writer)}, but the implementation may uses a more efficient solution.
+     */
+    public abstract void output(String textToEsc, Writer out) throws IOException, TemplateModelException;
+    
+    /**
+     * If this {@link TemplateMarkupOutputModel} was created with {@link #fromPlainTextByEscaping(String)}, it returns
+     * the original plain text, otherwise it returns {@code null}. Useful for converting between different types
+     * of markups, as if the source format can be converted to plain text without loss, then that just has to be
+     * re-escaped with the target format to do the conversion.
+     */
+    public abstract String getSourcePlainText(MO mo) throws TemplateModelException;
+
+    /**
+     * Returns the content as markup text; never {@code null}. If this {@link TemplateMarkupOutputModel} was created
+     * with {@link #fromMarkup(String)}, it might returns the original markup text literally, but this is not required
+     * as far as the returned markup means the same. If this {@link TemplateMarkupOutputModel} wasn't created
+     * with {@link #fromMarkup(String)} and it doesn't yet have the markup, it has to generate the markup now.
+     */
+    public abstract String getMarkupString(MO mo) throws TemplateModelException;
+    
+    /**
+     * Returns a {@link TemplateMarkupOutputModel} that contains the content of both {@link TemplateMarkupOutputModel}
+     * objects concatenated.
+     */
+    public abstract MO concat(MO mo1, MO mo2) throws TemplateModelException;
+    
+    /**
+     * Should give the same result as {@link #fromPlainTextByEscaping(String)} and then
+     * {@link #getMarkupString(TemplateMarkupOutputModel)}, but the implementation may uses a more efficient solution.
+     */
+    public abstract String escapePlainText(String plainTextContent) throws TemplateModelException;
+
+    /**
+     * Returns if the markup is empty (0 length). This is used by at least {@code ?hasContent}.
+     */
+    public abstract boolean isEmpty(MO mo) throws TemplateModelException;
+    
+    /**
+     * Tells if a string built-in that can't handle a {@link TemplateMarkupOutputModel} left hand operand can bypass
+     * this object as is. A typical such case would be when a {@link TemplateHTMLOutputModel} of "HTML" format bypasses
+     * {@code ?html}.
+     */
+    public abstract boolean isLegacyBuiltInBypassed(String builtInName) throws TemplateModelException;
+    
+    /**
+     * Tells if by default auto-escaping should be on for this format. It should be {@code true} if you need to escape
+     * on most of the places where you insert values.
+     * 
+     * @see Configuration#setAutoEscapingPolicy(int)
+     */
+    public abstract boolean isAutoEscapedByDefault();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MarkupOutputFormatBoundBuiltIn.java b/src/main/java/org/apache/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
new file mode 100644
index 0000000..b97663f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
@@ -0,0 +1,45 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+abstract class MarkupOutputFormatBoundBuiltIn extends SpecialBuiltIn {
+    
+    protected MarkupOutputFormat outputFormat;
+    
+    void bindToMarkupOutputFormat(MarkupOutputFormat outputFormat) {
+        _NullArgumentException.check(outputFormat);
+        this.outputFormat = outputFormat;
+    }
+    
+    @Override
+    TemplateModel _eval(Environment env) throws TemplateException {
+        if (outputFormat == null) {
+            // The parser should prevent this situation
+            throw new NullPointerException("outputFormat was null");
+        }
+        return calculateResult(env);
+    }
+
+    protected abstract TemplateModel calculateResult(Environment env)
+            throws TemplateException;
+    
+}
\ 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/MessageUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MessageUtil.java b/src/main/java/org/apache/freemarker/core/MessageUtil.java
new file mode 100644
index 0000000..6787411
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MessageUtil.java
@@ -0,0 +1,350 @@
+/*
+ * 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.ArrayList;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Utilities for creating error messages (and other messages).
+ */
+class MessageUtil {
+
+    static final String UNKNOWN_DATE_TO_STRING_ERROR_MESSAGE
+            = "Can't convert the date-like value to string because it isn't "
+              + "known if it's a date (no time part), time or date-time value.";
+    
+    static final String UNKNOWN_DATE_PARSING_ERROR_MESSAGE
+            = "Can't parse the string to date-like value because it isn't "
+              + "known if it's desired result should be a date (no time part), a time, or a date-time value.";
+
+    static final String UNKNOWN_DATE_TYPE_ERROR_TIP = 
+            "Use ?date, ?time, or ?datetime to tell FreeMarker the exact type.";
+    
+    static final Object[] UNKNOWN_DATE_TO_STRING_TIPS = {
+            UNKNOWN_DATE_TYPE_ERROR_TIP,
+            "If you need a particular format only once, use ?string(pattern), like ?string('dd.MM.yyyy HH:mm:ss'), "
+            + "to specify which fields to display. "
+    };
+
+    static final String EMBEDDED_MESSAGE_BEGIN = "---begin-message---\n";
+
+    static final String EMBEDDED_MESSAGE_END = "\n---end-message---";
+
+    // Can't be instantiated
+    private MessageUtil() { }
+        
+    static String formatLocationForSimpleParsingError(Template template, int line, int column) {
+        return formatLocation("in", template, line, column);
+    }
+
+    static String formatLocationForSimpleParsingError(String templateSourceName, int line, int column) {
+        return formatLocation("in", templateSourceName, line, column);
+    }
+
+    static String formatLocationForDependentParsingError(Template template, int line, int column) {
+        return formatLocation("on", template, line, column);
+    }
+
+    static String formatLocationForDependentParsingError(String templateSourceName, int line, int column) {
+        return formatLocation("on", templateSourceName, line, column);
+    }
+
+    static String formatLocationForEvaluationError(Template template, int line, int column) {
+        return formatLocation("at", template, line, column);
+    }
+
+    static String formatLocationForEvaluationError(ASTDirMacro macro, int line, int column) {
+        Template t = macro.getTemplate();
+        return formatLocation("at", t != null ? t.getSourceName() : null, macro.getName(), macro.isFunction(), line, column);
+    }
+    
+    static String formatLocationForEvaluationError(String templateSourceName, int line, int column) {
+        return formatLocation("at", templateSourceName, line, column);
+    }
+
+    private static String formatLocation(String preposition, Template template, int line, int column) {
+        return formatLocation(preposition, template != null ? template.getSourceName() : null, line, column);
+    }
+    
+    private static String formatLocation(String preposition, String templateSourceName, int line, int column) {
+        return formatLocation(
+                preposition, templateSourceName,
+                null, false,
+                line, column);
+    }
+
+    private static String formatLocation(
+            String preposition, String templateSourceName,
+            String macroOrFuncName, boolean isFunction,
+            int line, int column) {
+        String templateDesc;
+        if (line < 0) {
+            templateDesc = "?eval-ed string";
+            macroOrFuncName = null;
+        } else { 
+            templateDesc = templateSourceName != null
+                ? "template " + _StringUtil.jQuoteNoXSS(templateSourceName)
+                : "nameless template";
+        }
+        return "in " + templateDesc
+              + (macroOrFuncName != null
+                      ? " in " + (isFunction ? "function " : "macro ") + _StringUtil.jQuote(macroOrFuncName)
+                      : "")
+              + " "
+              + preposition + " " + formatPosition(line, column);
+    }
+    
+    static String formatPosition(int line, int column) {
+        return "line " + (line >= 0 ? line : line - (ASTNode.RUNTIME_EVAL_LINE_DISPLACEMENT - 1))
+                + ", column " + column;
+    }
+
+    /**
+     * Returns a single line string that is no longer than {@code maxLength}.
+     * If will truncate the string at line-breaks too.
+     * The truncation is always signaled with a a {@code "..."} at the end of the result string.  
+     */
+    static String shorten(String s, int maxLength) {
+        if (maxLength < 5) maxLength = 5;
+        
+        boolean isTruncated = false;
+        
+        int brIdx = s.indexOf('\n');
+        if (brIdx != -1) {
+            s = s.substring(0, brIdx);
+            isTruncated = true;
+        };
+        brIdx = s.indexOf('\r');
+        if (brIdx != -1) {
+            s = s.substring(0, brIdx);
+            isTruncated = true;
+        }
+        
+        if (s.length() > maxLength) {
+            s = s.substring(0, maxLength - 3);
+            isTruncated = true;
+        }
+        
+        if (!isTruncated) {
+            return s;
+        } else {
+            if (s.endsWith(".")) {
+                if (s.endsWith("..")) {
+                    if (s.endsWith("...")) {
+                        return s;
+                    } else {
+                        return s + ".";
+                    }
+                } else {
+                    return s + "..";
+                }
+            } else {
+                return s + "...";
+            }
+        }
+    }
+    
+    static StringBuilder appendExpressionAsUntearable(StringBuilder sb, ASTExpression argExp) {
+        boolean needParen =
+                !(argExp instanceof ASTExpNumberLiteral)
+                && !(argExp instanceof ASTExpStringLiteral)
+                && !(argExp instanceof ASTExpBooleanLiteral)
+                && !(argExp instanceof ASTExpListLiteral)
+                && !(argExp instanceof ASTExpHashLiteral)
+                && !(argExp instanceof ASTExpVariable)
+                && !(argExp instanceof ASTExpDot)
+                && !(argExp instanceof ASTExpDynamicKeyName)
+                && !(argExp instanceof ASTExpMethodCall)
+                && !(argExp instanceof ASTExpBuiltIn);
+        if (needParen) sb.append('(');
+        sb.append(argExp.getCanonicalForm());
+        if (needParen) sb.append(')');
+        return sb;
+    }
+
+    static TemplateModelException newArgCntError(String methodName, int argCnt, int expectedCnt) {
+        return newArgCntError(methodName, argCnt, expectedCnt, expectedCnt);
+    }
+    
+    static TemplateModelException newArgCntError(String methodName, int argCnt, int minCnt, int maxCnt) {
+        ArrayList/*<Object>*/ desc = new ArrayList(20);
+        
+        desc.add(methodName);
+        
+        desc.add("(");
+        if (maxCnt != 0) desc.add("...");
+        desc.add(") expects ");
+        
+        if (minCnt == maxCnt) {
+            if (maxCnt == 0) {
+                desc.add("no");
+            } else {
+                desc.add(Integer.valueOf(maxCnt));
+            }
+        } else if (maxCnt - minCnt == 1) {
+            desc.add(Integer.valueOf(minCnt));
+            desc.add(" or ");
+            desc.add(Integer.valueOf(maxCnt));
+        } else {
+            desc.add(Integer.valueOf(minCnt));
+            if (maxCnt != Integer.MAX_VALUE) {
+                desc.add(" to ");
+                desc.add(Integer.valueOf(maxCnt));
+            } else {
+                desc.add(" or more (unlimited)");
+            }
+        }
+        desc.add(" argument");
+        if (maxCnt > 1) desc.add("s");
+        
+        desc.add(" but has received ");
+        if (argCnt == 0) {
+            desc.add("none");
+        } else {
+            desc.add(Integer.valueOf(argCnt));
+        }
+        desc.add(".");
+        
+        return new _TemplateModelException(desc.toArray());
+    }
+
+    static TemplateModelException newMethodArgMustBeStringException(String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "string", arg);
+    }
+
+    static TemplateModelException newMethodArgMustBeNumberException(String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "number", arg);
+    }
+    
+    static TemplateModelException newMethodArgMustBeBooleanException(String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "boolean", arg);
+    }
+    
+    static TemplateModelException newMethodArgMustBeExtendedHashException(
+            String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "extended hash", arg);
+    }
+    
+    static TemplateModelException newMethodArgMustBeSequenceException(
+            String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "sequence", arg);
+    }
+    
+    static TemplateModelException newMethodArgMustBeSequenceOrCollectionException(
+            String methodName, int argIdx, TemplateModel arg) {
+        return newMethodArgUnexpectedTypeException(methodName, argIdx, "sequence or collection", arg);
+    }
+    
+    static TemplateModelException newMethodArgUnexpectedTypeException(
+            String methodName, int argIdx, String expectedType, TemplateModel arg) {
+        return new _TemplateModelException(
+                methodName, "(...) expects ", new _DelayedAOrAn(expectedType), " as argument #", Integer.valueOf(argIdx + 1),
+                ", but received ", new _DelayedAOrAn(new _DelayedFTLTypeDescription(arg)), ".");
+    }
+    
+    /**
+     * The type of the argument was good, but it's value wasn't.
+     */
+    static TemplateModelException newMethodArgInvalidValueException(
+            String methodName, int argIdx, Object... details) {
+        return new _TemplateModelException(
+                methodName, "(...) argument #", Integer.valueOf(argIdx + 1),
+                " had invalid value: ", details);
+    }
+
+    /**
+     * The type of the argument was good, but the values of two or more arguments are inconsistent with each other.
+     */
+    static TemplateModelException newMethodArgsInvalidValueException(
+            String methodName, Object... details) {
+        return new _TemplateModelException(methodName, "(...) arguments have invalid value: ", details);
+    }
+    
+    static TemplateException newInstantiatingClassNotAllowedException(String className, Environment env) {
+        return new _MiscTemplateException(env,
+                "Instantiating ", className, " is not allowed in the template for security reasons.");
+    }
+    
+    static _TemplateModelException newCantFormatUnknownTypeDateException(
+            ASTExpression dateSourceExpr, UnknownDateTypeFormattingUnsupportedException cause) {
+        return new _TemplateModelException(cause, null, new _ErrorDescriptionBuilder(
+                MessageUtil.UNKNOWN_DATE_TO_STRING_ERROR_MESSAGE)
+                .blame(dateSourceExpr)
+                .tips(MessageUtil.UNKNOWN_DATE_TO_STRING_TIPS));
+    }
+
+    static TemplateException newCantFormatDateException(TemplateDateFormat format, ASTExpression dataSrcExp,
+            TemplateValueFormatException e, boolean useTempModelExc) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Failed to format date/time/datetime with format ", new _DelayedJQuote(format.getDescription()), ": ",
+                e.getMessage())
+                .blame(dataSrcExp); 
+        return useTempModelExc
+                ? new _TemplateModelException(e, null, desc)
+                : new _MiscTemplateException(e, null, desc);
+    }
+    
+    static TemplateException newCantFormatNumberException(TemplateNumberFormat format, ASTExpression dataSrcExp,
+            TemplateValueFormatException e, boolean useTempModelExc) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Failed to format number with format ", new _DelayedJQuote(format.getDescription()), ": ",
+                e.getMessage())
+                .blame(dataSrcExp); 
+        return useTempModelExc
+                ? new _TemplateModelException(e, null, desc)
+                : new _MiscTemplateException(e, null, desc);
+    }
+    
+    /**
+     * @return "a" or "an" or "a(n)" (or "" for empty string) for an FTL type name
+     */
+    static String getAOrAn(String s) {
+        if (s == null) return null;
+        if (s.length() == 0) return "";
+        
+        char fc = Character.toLowerCase(s.charAt(0));
+        if (fc == 'a' || fc == 'e' || fc == 'i') {
+            return "an";
+        } else if (fc == 'h') { 
+            String ls = s.toLowerCase();
+            if (ls.startsWith("has") || ls.startsWith("hi")) { 
+                return "a";
+            } else if (ls.startsWith("ht")) { 
+                return "an";
+            } else {
+                return "a(n)";
+            }
+        } else if (fc == 'u' || fc == 'o') {
+            return "a(n)";
+        } else {
+            char sc = (s.length() > 1) ? s.charAt(1) : '\0'; 
+            if (fc == 'x' && !(sc == 'a' || sc == 'e' || sc == 'i' || sc == 'a' || sc == 'o' || sc == 'u')) {
+                return "an";
+            } else {
+                return "a";
+            }
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/MiscUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MiscUtil.java b/src/main/java/org/apache/freemarker/core/MiscUtil.java
new file mode 100644
index 0000000..f3ba2bc
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/MiscUtil.java
@@ -0,0 +1,69 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utilities that didn't fit elsewhere. 
+ */
+class MiscUtil {
+    
+    // Can't be instatiated
+    private MiscUtil() { }
+
+    static final String C_FALSE = "false";
+    static final String C_TRUE = "true";
+    
+    /**
+     * Returns the map entries in source code order of the ASTExpression values.
+     */
+    static List/*Map.Entry*/ sortMapOfExpressions(Map/*<?, ASTExpression>*/ map) {
+        ArrayList res = new ArrayList(map.entrySet());
+        Collections.sort(res, 
+                new Comparator() {  // for sorting to source code order
+                    @Override
+                    public int compare(Object o1, Object o2) {
+                        Map.Entry ent1 = (Map.Entry) o1;
+                        ASTExpression exp1 = (ASTExpression) ent1.getValue();
+                        
+                        Map.Entry ent2 = (Map.Entry) o2;
+                        ASTExpression exp2 = (ASTExpression) ent2.getValue();
+                        
+                        int res = exp1.beginLine - exp2.beginLine;
+                        if (res != 0) return res;
+                        res = exp1.beginColumn - exp2.beginColumn;
+                        if (res != 0) return res;
+                        
+                        if (ent1 == ent2) return 0;
+                        
+                        // Should never reach this
+                        return ((String) ent1.getKey()).compareTo((String) ent1.getKey()); 
+                    }
+            
+        });
+        return res;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java b/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java
new file mode 100644
index 0000000..26504bd
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java
@@ -0,0 +1,67 @@
+/*
+ * 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 org.apache.freemarker.core.Environment.NestedElementTemplateDirectiveBody;
+import org.apache.freemarker.core.ThreadInterruptionSupportTemplatePostProcessor.ASTThreadInterruptionCheck;
+import org.apache.freemarker.core.model.TemplateDirectiveBody;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * [2.4] Should become public somehow; this is more intelligent than a {@code null} check, for example, when the body
+ * only contains a thread interruption check, it treats it as valid. Indicates that the directive shouldn't have
+ * nested content, but it had. This will probably become public when the directive/method stuff was reworked.
+ */
+class NestedContentNotSupportedException extends TemplateException {
+
+    public static void check(TemplateDirectiveBody body) throws NestedContentNotSupportedException {
+        if (body == null) {
+            return;
+        }
+        if (body instanceof NestedElementTemplateDirectiveBody) {
+            _ASTElement[] tes = ((NestedElementTemplateDirectiveBody) body).getChildrenBuffer();
+            if (tes == null || tes.length == 0
+                    || tes[0] instanceof ASTThreadInterruptionCheck && (tes.length == 1 || tes[1] == null)) {
+                return;
+            }
+        }
+        throw new NestedContentNotSupportedException(Environment.getCurrentEnvironment());
+    }
+    
+    
+    private NestedContentNotSupportedException(Environment env) {
+        this(null, null, env);
+    }
+
+    private NestedContentNotSupportedException(Exception cause, Environment env) {
+        this(null, cause, env);
+    }
+
+    private NestedContentNotSupportedException(String description, Environment env) {
+        this(description, null, env);
+    }
+
+    private NestedContentNotSupportedException(String description, Exception cause, Environment env) {
+        super( "Nested content (body) not supported."
+                + (description != null ? " " + _StringUtil.jQuote(description) : ""),
+                cause, env);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonBooleanException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonBooleanException.java b/src/main/java/org/apache/freemarker/core/NonBooleanException.java
new file mode 100644
index 0000000..2627ad0
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonBooleanException.java
@@ -0,0 +1,62 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link TemplateBooleanModel} value was expected, but the value had a different type.
+ */
+public class NonBooleanException extends UnexpectedTypeException {
+    
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateBooleanModel.class }; 
+
+    public NonBooleanException(Environment env) {
+        super(env, "Expecting boolean value here");
+    }
+
+    public NonBooleanException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonBooleanException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonBooleanException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "boolean", EXPECTED_TYPES, env);
+    }
+
+    NonBooleanException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "boolean", EXPECTED_TYPES, tip, env);
+    }
+
+    NonBooleanException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "boolean", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonDateException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonDateException.java b/src/main/java/org/apache/freemarker/core/NonDateException.java
new file mode 100644
index 0000000..8014c34
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonDateException.java
@@ -0,0 +1,58 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link TemplateDateModel} value was expected, but the value had a different type.
+ */
+public class NonDateException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateDateModel.class };
+
+    public NonDateException(Environment env) {
+        super(env, "Expecting date/time value here");
+    }
+
+    public NonDateException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonDateException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "date/time", EXPECTED_TYPES, env);
+    }
+
+    NonDateException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "date/time", EXPECTED_TYPES, tip, env);
+    }
+
+    NonDateException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "date/time", EXPECTED_TYPES, tips, env);
+    }    
+        
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonExtendedHashException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonExtendedHashException.java b/src/main/java/org/apache/freemarker/core/NonExtendedHashException.java
new file mode 100644
index 0000000..e50d956
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonExtendedHashException.java
@@ -0,0 +1,62 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link TemplateHashModelEx} value was expected, but the value had a different type.
+ */
+public class NonExtendedHashException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateHashModelEx.class };
+    
+    public NonExtendedHashException(Environment env) {
+        super(env, "Expecting extended hash value here");
+    }
+
+    public NonExtendedHashException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonExtendedHashException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonExtendedHashException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "extended hash", EXPECTED_TYPES, env);
+    }
+
+    NonExtendedHashException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "extended hash", EXPECTED_TYPES, tip, env);
+    }
+
+    NonExtendedHashException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "extended hash", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonExtendedNodeException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonExtendedNodeException.java b/src/main/java/org/apache/freemarker/core/NonExtendedNodeException.java
new file mode 100644
index 0000000..134e7b6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonExtendedNodeException.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNodeModelEx;
+
+/**
+ * Indicates that a {@link TemplateNodeModelEx} value was expected, but the value had a different type.
+ * 
+ * @since 2.3.26
+ */
+public class NonExtendedNodeException extends UnexpectedTypeException {
+
+    private static final Class<?>[] EXPECTED_TYPES = new Class[] { TemplateNodeModelEx.class };
+    
+    public NonExtendedNodeException(Environment env) {
+        super(env, "Expecting extended node value here");
+    }
+
+    public NonExtendedNodeException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonExtendedNodeException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonExtendedNodeException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "extended node", EXPECTED_TYPES, env);
+    }
+
+    NonExtendedNodeException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "extended node", EXPECTED_TYPES, tip, env);
+    }
+
+    NonExtendedNodeException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "extended node", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonHashException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonHashException.java b/src/main/java/org/apache/freemarker/core/NonHashException.java
new file mode 100644
index 0000000..48f28fa
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonHashException.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link TemplateHashModel} value was expected, but the value had a different type.
+ * 
+ * @since 2.3.21
+ */
+public class NonHashException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateHashModel.class };
+    
+    public NonHashException(Environment env) {
+        super(env, "Expecting hash value here");
+    }
+
+    public NonHashException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonHashException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonHashException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "hash", EXPECTED_TYPES, env);
+    }
+
+    NonHashException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "hash", EXPECTED_TYPES, tip, env);
+    }
+
+    NonHashException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "hash", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonMarkupOutputException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonMarkupOutputException.java b/src/main/java/org/apache/freemarker/core/NonMarkupOutputException.java
new file mode 100644
index 0000000..6f69980
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonMarkupOutputException.java
@@ -0,0 +1,63 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link TemplateMarkupOutputModel} value was expected, but the value had a different type.
+ * 
+ * @since 2.3.24
+ */
+public class NonMarkupOutputException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateMarkupOutputModel.class };
+    
+    public NonMarkupOutputException(Environment env) {
+        super(env, "Expecting markup output value here");
+    }
+
+    public NonMarkupOutputException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonMarkupOutputException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonMarkupOutputException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "markup output", EXPECTED_TYPES, env);
+    }
+
+    NonMarkupOutputException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "markup output", EXPECTED_TYPES, tip, env);
+    }
+
+    NonMarkupOutputException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "markup output", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonMethodException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonMethodException.java b/src/main/java/org/apache/freemarker/core/NonMethodException.java
new file mode 100644
index 0000000..d8f4d5a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonMethodException.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link TemplateMethodModel} value was expected, but the value had a different type.
+ * 
+ * @since 2.3.21
+ */
+public class NonMethodException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateMethodModel.class };
+    
+    public NonMethodException(Environment env) {
+        super(env, "Expecting method value here");
+    }
+
+    public NonMethodException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonMethodException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonMethodException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "method", EXPECTED_TYPES, env);
+    }
+
+    NonMethodException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "method", EXPECTED_TYPES, tip, env);
+    }
+
+    NonMethodException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "method", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonNamespaceException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonNamespaceException.java b/src/main/java/org/apache/freemarker/core/NonNamespaceException.java
new file mode 100644
index 0000000..8834483
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonNamespaceException.java
@@ -0,0 +1,63 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link Environment.Namespace} value was expected, but the value had a different type.
+ * 
+ * @since 2.3.21
+ */
+class NonNamespaceException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { Environment.Namespace.class };
+    
+    public NonNamespaceException(Environment env) {
+        super(env, "Expecting namespace value here");
+    }
+
+    public NonNamespaceException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonNamespaceException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonNamespaceException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "namespace", EXPECTED_TYPES, env);
+    }
+
+    NonNamespaceException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "namespace", EXPECTED_TYPES, tip, env);
+    }
+
+    NonNamespaceException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "namespace", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonNodeException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonNodeException.java b/src/main/java/org/apache/freemarker/core/NonNodeException.java
new file mode 100644
index 0000000..8ef9213
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonNodeException.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNodeModel;
+
+/**
+ * Indicates that a {@link TemplateNodeModel} value was expected, but the value had a different type.
+ * 
+ * @since 2.3.21
+ */
+public class NonNodeException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateNodeModel.class };
+    
+    public NonNodeException(Environment env) {
+        super(env, "Expecting node value here");
+    }
+
+    public NonNodeException(String description, Environment env) {
+        super(env, description);
+    }
+
+    NonNodeException(Environment env, _ErrorDescriptionBuilder description) {
+        super(env, description);
+    }
+
+    NonNodeException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "node", EXPECTED_TYPES, env);
+    }
+
+    NonNodeException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "node", EXPECTED_TYPES, tip, env);
+    }
+
+    NonNodeException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "node", EXPECTED_TYPES, tips, env);
+    }    
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/NonNumericalException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/NonNumericalException.java b/src/main/java/org/apache/freemarker/core/NonNumericalException.java
new file mode 100644
index 0000000..cd13f05
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/NonNumericalException.java
@@ -0,0 +1,74 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+/**
+ * Indicates that a {@link TemplateNumberModel} value was expected, but the value had a different type.
+ */
+public class NonNumericalException extends UnexpectedTypeException {
+
+    private static final Class[] EXPECTED_TYPES = new Class[] { TemplateNumberModel.class };
+    
+    public NonNumericalException(Environment env) {
+        super(env, "Expecting numerical value here");
+    }
+
+    public NonNumericalException(String description, Environment env) {
+        super(env, description);
+    }
+ 
+    NonNumericalException(_ErrorDescriptionBuilder description, Environment env) {
+        super(env, description);
+    }
+
+    NonNumericalException(
+            ASTExpression blamed, TemplateModel model, Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "number", EXPECTED_TYPES, env);
+    }
+
+    NonNumericalException(
+            ASTExpression blamed, TemplateModel model, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(blamed, model, "number", EXPECTED_TYPES, tip, env);
+    }
+
+    NonNumericalException(
+            ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+        super(blamed, model, "number", EXPECTED_TYPES, tips, env);
+    }
+
+    NonNumericalException(
+            String assignmentTargetVarName, TemplateModel model, String[] tips, Environment env)
+            throws InvalidReferenceException {
+        super(assignmentTargetVarName, model, "number", EXPECTED_TYPES, tips, env);
+    }
+    static NonNumericalException newMalformedNumberException(ASTExpression blamed, String text, Environment env) {
+        return new NonNumericalException(
+                new _ErrorDescriptionBuilder("Can't convert this string to number: ", new _DelayedJQuote(text))
+                .blame(blamed),
+                env);
+    }
+    
+}



Mime
View raw message