http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java
new file mode 100644
index 0000000..e3270c3
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java
@@ -0,0 +1,112 @@
+/*
+ * 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.model.impl;
+
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.slf4j.Logger;
+
+class UnsafeMethods {
+    
+    private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
+    private static final String UNSAFE_METHODS_PROPERTIES = 
"unsafeMethods.properties";
+    private static final Set UNSAFE_METHODS = createUnsafeMethodsSet();
+    
+    private UnsafeMethods() { }
+    
+    static boolean isUnsafeMethod(Method method) {
+        return UNSAFE_METHODS.contains(method);        
+    }
+    
+    private static Set createUnsafeMethodsSet() {
+        Properties props = new Properties();
+        InputStream in = 
DefaultObjectWrapper.class.getResourceAsStream(UNSAFE_METHODS_PROPERTIES);
+        if (in == null) {
+            throw new IllegalStateException("Class loader resource not found: "
+                        + DefaultObjectWrapper.class.getPackage().getName() + 
UNSAFE_METHODS_PROPERTIES);
+        }
+        String methodSpec = null;
+        try {
+            try {
+                props.load(in);
+            } finally {
+                in.close();
+            }
+            Set set = new HashSet(props.size() * 4 / 3, 1f);
+            Map primClasses = createPrimitiveClassesMap();
+            for (Iterator iterator = props.keySet().iterator(); 
iterator.hasNext(); ) {
+                methodSpec = (String) iterator.next();
+                try {
+                    set.add(parseMethodSpec(methodSpec, primClasses));
+                } catch (ClassNotFoundException | NoSuchMethodException e) {
+                    LOG.debug("Failed to get unsafe method", e);
+                }
+            }
+            return set;
+        } catch (Exception e) {
+            throw new RuntimeException("Could not load unsafe method " + 
methodSpec + " " + e.getClass().getName() + " " + e.getMessage());
+        }
+    }
+
+    private static Method parseMethodSpec(String methodSpec, Map primClasses)
+    throws ClassNotFoundException,
+        NoSuchMethodException {
+        int brace = methodSpec.indexOf('(');
+        int dot = methodSpec.lastIndexOf('.', brace);
+        Class clazz = _ClassUtil.forName(methodSpec.substring(0, dot));
+        String methodName = methodSpec.substring(dot + 1, brace);
+        String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 
1);
+        StringTokenizer tok = new StringTokenizer(argSpec, ",");
+        int argcount = tok.countTokens();
+        Class[] argTypes = new Class[argcount];
+        for (int i = 0; i < argcount; i++) {
+            String argClassName = tok.nextToken();
+            argTypes[i] = (Class) primClasses.get(argClassName);
+            if (argTypes[i] == null) {
+                argTypes[i] = _ClassUtil.forName(argClassName);
+            }
+        }
+        return clazz.getMethod(methodName, argTypes);
+    }
+
+    private static Map createPrimitiveClassesMap() {
+        Map map = new HashMap();
+        map.put("boolean", Boolean.TYPE);
+        map.put("byte", Byte.TYPE);
+        map.put("char", Character.TYPE);
+        map.put("short", Short.TYPE);
+        map.put("int", Integer.TYPE);
+        map.put("long", Long.TYPE);
+        map.put("float", Float.TYPE);
+        map.put("double", Double.TYPE);
+        return map;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java
new file mode 100644
index 0000000..82da455
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java
@@ -0,0 +1,319 @@
+/*
+ * 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.model.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.freemarker.core._DelayedConversionToString;
+import org.apache.freemarker.core._DelayedJQuote;
+import org.apache.freemarker.core._TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._ClassUtil;
+
+/**
+ * For internal use only; don't depend on this, there's no backward 
compatibility guarantee at all!
+ */
+public final class _MethodUtil {
+    
+    private _MethodUtil() {
+        // Not meant to be instantiated
+    }
+
+    /**
+     * Determines whether the type given as the 1st argument is convertible to 
the type given as the 2nd argument
+     * for method call argument conversion. This follows the rules of the Java 
reflection-based method call, except
+     * that since we don't have the value here, a boxed class is never seen as 
convertible to a primitive type. 
+     * 
+     * @return 0 means {@code false}, non-0 means {@code true}.
+     *         That is, 0 is returned less specificity or incomparable 
specificity, also when if
+     *         then method was aborted because of {@code ifHigherThan}.
+     *         The absolute value of the returned non-0 number symbolizes how 
more specific it is:
+     *         <ul>
+     *           <li>1: The two classes are identical</li>
+     *           <li>2: The 1st type is primitive, the 2nd type is the 
corresponding boxing class</li>
+     *           <li>3: Both classes are numerical, and one is convertible 
into the other with widening conversion.
+     *                  E.g., {@code int} is convertible to {@code long} and 
{#code double}, hence {@code int} is more
+     *                  specific.
+     *                  This ignores primitive VS boxed mismatches, except 
that a boxed class is never seen as
+     *                  convertible to a primitive class.</li>
+     *           <li>4: One class is {@code instanceof} of the other, but they 
aren't identical.
+     *               But unlike in Java, primitive numerical types are {@code 
instanceof} {@link Number} here.</li>
+     *         </ul> 
+     */
+    // TODO Seems that we don't use the full functionality of this anymore, so 
we could simplify this. See usages.
+    public static int isMoreOrSameSpecificParameterType(final Class specific, 
final Class generic, boolean bugfixed,
+            int ifHigherThan) {
+        if (ifHigherThan >= 4) return 0;
+        if (generic.isAssignableFrom(specific)) {
+            // Identity or widening reference conversion:
+            return generic == specific ? 1 : 4;
+        } else {
+            final boolean specificIsPrim = specific.isPrimitive(); 
+            final boolean genericIsPrim = generic.isPrimitive();
+            if (specificIsPrim) {
+                if (genericIsPrim) {
+                    if (ifHigherThan >= 3) return 0;
+                    return isWideningPrimitiveNumberConversion(specific, 
generic) ? 3 : 0;
+                } else {  // => specificIsPrim && !genericIsPrim
+                    if (bugfixed) {
+                        final Class specificAsBoxed = 
_ClassUtil.primitiveClassToBoxingClass(specific);
+                        if (specificAsBoxed == generic) {
+                            // A primitive class is more specific than its 
boxing class, because it can't store null
+                            return 2;
+                        } else if (generic.isAssignableFrom(specificAsBoxed)) {
+                            // Note: This only occurs if `specific` is a 
primitive numerical, and `generic == Number`
+                            return 4;
+                        } else if (ifHigherThan >= 3) {
+                            return 0;
+                        } else if 
(Number.class.isAssignableFrom(specificAsBoxed)
+                                && Number.class.isAssignableFrom(generic)) {
+                            return 
isWideningBoxedNumberConversion(specificAsBoxed, generic) ? 3 : 0;
+                        } else {
+                            return 0;
+                        }
+                    } else {
+                        return 0;
+                    }
+                }
+            } else {  // => !specificIsPrim
+                if (ifHigherThan >= 3) return 0;
+                if (bugfixed && !genericIsPrim
+                        && Number.class.isAssignableFrom(specific) && 
Number.class.isAssignableFrom(generic)) {
+                    return isWideningBoxedNumberConversion(specific, generic) 
? 3 : 0;
+                } else {
+                    return 0;
+                }
+            }
+        }  // of: !generic.isAssignableFrom(specific) 
+    }
+
+    private static boolean isWideningPrimitiveNumberConversion(final Class 
source, final Class target) {
+        if (target == Short.TYPE && (source == Byte.TYPE)) {
+            return true;
+        } else if (target == Integer.TYPE && 
+           (source == Short.TYPE || source == Byte.TYPE)) {
+            return true;
+        } else if (target == Long.TYPE && 
+           (source == Integer.TYPE || source == Short.TYPE || 
+            source == Byte.TYPE)) {
+            return true;
+        } else if (target == Float.TYPE && 
+           (source == Long.TYPE || source == Integer.TYPE || 
+            source == Short.TYPE || source == Byte.TYPE)) {
+            return true;
+        } else if (target == Double.TYPE && 
+           (source == Float.TYPE || source == Long.TYPE || 
+            source == Integer.TYPE || source == Short.TYPE || 
+            source == Byte.TYPE)) {
+            return true; 
+        } else {
+            return false;
+        }
+    }
+
+    private static boolean isWideningBoxedNumberConversion(final Class source, 
final Class target) {
+        if (target == Short.class && source == Byte.class) {
+            return true;
+        } else if (target == Integer.class && 
+           (source == Short.class || source == Byte.class)) {
+            return true;
+        } else if (target == Long.class && 
+           (source == Integer.class || source == Short.class || 
+            source == Byte.class)) {
+            return true;
+        } else if (target == Float.class && 
+           (source == Long.class || source == Integer.class || 
+            source == Short.class || source == Byte.class)) {
+            return true;
+        } else if (target == Double.class && 
+           (source == Float.class || source == Long.class || 
+            source == Integer.class || source == Short.class || 
+            source == Byte.class)) {
+            return true; 
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Attention, this doesn't handle primitive classes correctly, nor 
numerical conversions.
+     */
+    public static Set getAssignables(Class c1, Class c2) {
+        Set s = new HashSet();
+        collectAssignables(c1, c2, s);
+        return s;
+    }
+    
+    private static void collectAssignables(Class c1, Class c2, Set s) {
+        if (c1.isAssignableFrom(c2)) {
+            s.add(c1);
+        }
+        Class sc = c1.getSuperclass();
+        if (sc != null) {
+            collectAssignables(sc, c2, s);
+        }
+        Class[] itf = c1.getInterfaces();
+        for (Class anItf : itf) {
+            collectAssignables(anItf, c2, s);
+        }
+    }
+
+    public static Class[] getParameterTypes(Member member) {
+        if (member instanceof Method) {
+            return ((Method) member).getParameterTypes();
+        }
+        if (member instanceof Constructor) {
+            return ((Constructor) member).getParameterTypes();
+        }
+        throw new IllegalArgumentException("\"member\" must be Method or 
Constructor");
+    }
+
+    public static boolean isVarargs(Member member) {
+        if (member instanceof Method) { 
+            return ((Method) member).isVarArgs();
+        }
+        if (member instanceof Constructor) {
+            return ((Constructor) member).isVarArgs();
+        }
+        throw new BugException();
+    }
+
+    /**
+     * Returns a more streamlined method or constructor description than 
{@code Member.toString()} does.
+     */
+    public static String toString(Member member) {
+        if (!(member instanceof Method || member instanceof Constructor)) {
+            throw new IllegalArgumentException("\"member\" must be a Method or 
Constructor");
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        
+        if ((member.getModifiers() & Modifier.STATIC) != 0) {
+            sb.append("static ");
+        }
+        
+        String className = 
_ClassUtil.getShortClassName(member.getDeclaringClass());
+        if (className != null) {
+            sb.append(className);
+            sb.append('.');
+        }
+        sb.append(member.getName());
+
+        sb.append('(');
+        Class[] paramTypes = _MethodUtil.getParameterTypes(member);
+        for (int i = 0; i < paramTypes.length; i++) {
+            if (i != 0) sb.append(", ");
+            String paramTypeDecl = _ClassUtil.getShortClassName(paramTypes[i]);
+            if (i == paramTypes.length - 1 && paramTypeDecl.endsWith("[]") && 
_MethodUtil.isVarargs(member)) {
+                sb.append(paramTypeDecl.substring(0, paramTypeDecl.length() - 
2));
+                sb.append("...");
+            } else {
+                sb.append(paramTypeDecl);
+            }
+        }
+        sb.append(')');
+        
+        return sb.toString();
+    }
+
+    public static Object[] invocationErrorMessageStart(Member member) {
+        return invocationErrorMessageStart(member, member instanceof 
Constructor);
+    }
+    
+    private static Object[] invocationErrorMessageStart(Object member, boolean 
isConstructor) {
+        return new Object[] { "Java ", isConstructor ? "constructor " : 
"method ", new _DelayedJQuote(member) };
+    }
+
+    public static TemplateModelException 
newInvocationTemplateModelException(Object object, Member member, Throwable e) {
+        return newInvocationTemplateModelException(
+                object,
+                member,
+                (member.getModifiers() & Modifier.STATIC) != 0,
+                member instanceof Constructor,
+                e);
+    }
+
+    public static TemplateModelException 
newInvocationTemplateModelException(Object object, CallableMemberDescriptor 
callableMemberDescriptor, Throwable e) {
+        return newInvocationTemplateModelException(
+                object,
+                new _DelayedConversionToString(callableMemberDescriptor) {
+                    @Override
+                    protected String doConversion(Object 
callableMemberDescriptor) {
+                        return ((CallableMemberDescriptor) 
callableMemberDescriptor).getDeclaration();
+                    }
+                },
+                callableMemberDescriptor.isStatic(),
+                callableMemberDescriptor.isConstructor(),
+                e);
+    }
+    
+    private static TemplateModelException newInvocationTemplateModelException(
+            Object parentObject, Object member, boolean isStatic, boolean 
isConstructor, Throwable e) {
+        while (e instanceof InvocationTargetException) {
+            Throwable cause = ((InvocationTargetException) 
e).getTargetException();
+            if (cause != null) {
+                e = cause;
+            } else {
+                break;
+            }
+        }
+
+        return new _TemplateModelException(e,
+                invocationErrorMessageStart(member, isConstructor),
+                " threw an exception",
+                isStatic || isConstructor ? "" : new Object[] {
+                    " when invoked on ", parentObject.getClass(), " object ", 
new _DelayedJQuote(parentObject) 
+                },
+                "; see cause exception in the Java stack trace.");
+    }
+
+    /**
+     * Extracts the JavaBeans property from a reader method name, or returns 
{@code null} if the method name doesn't
+     * look like a reader method name.
+     */
+    public static String getBeanPropertyNameFromReaderMethodName(String name, 
Class<?> returnType) {
+        int start;
+        if (name.startsWith("get")) {
+            start = 3;
+        } else if (returnType == boolean.class && name.startsWith("is")) {
+            start = 2;
+        } else {
+            return null;
+        }
+        int ln = name.length();
+
+        if (start == ln) {
+            return null;
+        }
+        char c1 = name.charAt(start);
+
+        return start + 1 < ln && Character.isUpperCase(name.charAt(start + 1)) 
&& Character.isUpperCase(c1)
+                ? name.substring(start) // getFOOBar => "FOOBar" (not lower 
case) according the JavaBeans spec.
+                : new StringBuilder(ln - 
start).append(Character.toLowerCase(c1)).append(name, start + 1, ln)
+                .toString();
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java
new file mode 100644
index 0000000..fecb0b0
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java
@@ -0,0 +1,122 @@
+/*
+ * 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.model.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._CollectionUtil;
+
+/**
+ * For internal use only; don't depend on this, there's no backward 
compatibility guarantee at all!
+ * This class is to work around the lack of module system in Java, i.e., so 
that other FreeMarker packages can
+ * access things inside this package that users shouldn't. 
+ */ 
+public class _ModelAPI {
+
+    private _ModelAPI() { }
+    
+    public static Object newInstance(Class<?> pClass, Object[] args, 
DefaultObjectWrapper ow)
+            throws NoSuchMethodException, IllegalArgumentException, 
InstantiationException,
+            IllegalAccessException, InvocationTargetException, 
TemplateModelException {
+        return newInstance(getConstructorDescriptor(pClass, args), args, ow);
+    }
+    
+    /**
+     * Gets the constructor that matches the types of the arguments the best. 
So this is more
+     * than what the Java reflection API provides in that it can handle 
overloaded constructors. This re-uses the
+     * overloaded method selection logic of {@link DefaultObjectWrapper}.
+     */
+    private static CallableMemberDescriptor getConstructorDescriptor(Class<?> 
pClass, Object[] args)
+            throws NoSuchMethodException {
+        if (args == null) args = _CollectionUtil.EMPTY_OBJECT_ARRAY;
+        
+        final ArgumentTypes argTypes = new ArgumentTypes(args);
+        final List<ReflectionCallableMemberDescriptor> fixedArgMemberDescs
+                = new ArrayList<>();
+        final List<ReflectionCallableMemberDescriptor> varArgsMemberDescs
+                = new ArrayList<>();
+        for (Constructor<?> constr : pClass.getConstructors()) {
+            ReflectionCallableMemberDescriptor memberDesc = new 
ReflectionCallableMemberDescriptor(constr, constr.getParameterTypes());
+            if (!_MethodUtil.isVarargs(constr)) {
+                fixedArgMemberDescs.add(memberDesc);
+            } else {
+                varArgsMemberDescs.add(memberDesc);
+            }
+        }
+        
+        MaybeEmptyCallableMemberDescriptor contrDesc = 
argTypes.getMostSpecific(fixedArgMemberDescs, false);
+        if (contrDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) {
+            contrDesc = argTypes.getMostSpecific(varArgsMemberDescs, true);
+        }
+        
+        if (contrDesc instanceof EmptyCallableMemberDescriptor) {
+            if (contrDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) {
+                throw new NoSuchMethodException(
+                        "There's no public " + pClass.getName()
+                        + " constructor with compatible parameter list.");
+            } else if (contrDesc == 
EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD) {
+                throw new NoSuchMethodException(
+                        "There are multiple public " + pClass.getName()
+                        + " constructors that match the compatible parameter 
list with the same preferability.");
+            } else {
+                throw new NoSuchMethodException();
+            }
+        } else {
+            return (CallableMemberDescriptor) contrDesc;
+        }
+    }
+    
+    private static Object newInstance(CallableMemberDescriptor constrDesc, 
Object[] args, DefaultObjectWrapper ow)
+            throws InstantiationException, IllegalAccessException, 
InvocationTargetException, IllegalArgumentException,
+            TemplateModelException {
+        if (args == null) args = _CollectionUtil.EMPTY_OBJECT_ARRAY;
+        
+        final Object[] packedArgs;
+        if (constrDesc.isVarargs()) {
+            // We have to put all the varargs arguments into a single array 
argument.
+
+            final Class<?>[] paramTypes = constrDesc.getParamTypes();
+            final int fixedArgCnt = paramTypes.length - 1;
+            
+            packedArgs = new Object[fixedArgCnt + 1]; 
+            for (int i = 0; i < fixedArgCnt; i++) {
+                packedArgs[i] = args[i];
+            }
+            
+            final Class<?> compType = 
paramTypes[fixedArgCnt].getComponentType();
+            final int varArgCnt = args.length - fixedArgCnt;
+            final Object varArgsArray = Array.newInstance(compType, varArgCnt);
+            for (int i = 0; i < varArgCnt; i++) {
+                Array.set(varArgsArray, i, args[fixedArgCnt + i]);
+            }
+            packedArgs[fixedArgCnt] = varArgsArray;
+        } else {
+            packedArgs = args;
+        }
+        
+        return constrDesc.invokeConstructor(ow, packedArgs);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/package.html
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/package.html
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/package.html
new file mode 100644
index 0000000..b3db746
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/package.html
@@ -0,0 +1,26 @@
+<!--
+  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.
+-->
+<html>
+<head>
+</head>
+<body>
+<p>Data model and template language type system: Standard implementations. 
This package is part of the
+published API, that is, user code can safely depend on it.</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/package.html
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/package.html 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/package.html
new file mode 100644
index 0000000..a2a9cfe
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/package.html
@@ -0,0 +1,25 @@
+<!--
+  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.
+-->
+<html>
+<head>
+</head>
+<body>
+<p>Data model and template language type system: Base classes/interfaces.</p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/CommonMarkupOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/CommonMarkupOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/CommonMarkupOutputFormat.java
new file mode 100644
index 0000000..760f28b
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/CommonMarkupOutputFormat.java
@@ -0,0 +1,124 @@
+/*
+ * 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.outputformat;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Common superclass for implementing {@link MarkupOutputFormat}-s that use a 
{@link CommonTemplateMarkupOutputModel}
+ * subclass.
+ * 
+ * @since 2.3.24
+ */
+public abstract class CommonMarkupOutputFormat<MO extends 
CommonTemplateMarkupOutputModel>
+        extends MarkupOutputFormat<MO> {
+
+    protected CommonMarkupOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public final MO fromPlainTextByEscaping(String textToEsc) throws 
TemplateModelException {
+        return newTemplateMarkupOutputModel(textToEsc, null);
+    }
+
+    @Override
+    public final MO fromMarkup(String markupText) throws 
TemplateModelException {
+        return newTemplateMarkupOutputModel(null, markupText);
+    }
+
+    @Override
+    public final void output(MO mo, Writer out) throws IOException, 
TemplateModelException {
+        String mc = mo.getMarkupContent();
+        if (mc != null) {
+            out.write(mc);
+        } else {
+            output(mo.getPlainTextContent(), out);
+        }
+    }
+
+    @Override
+    public abstract void output(String textToEsc, Writer out) throws 
IOException, TemplateModelException;
+    
+    @Override
+    public final String getSourcePlainText(MO mo) throws 
TemplateModelException {
+        return mo.getPlainTextContent();
+    }
+
+    @Override
+    public final String getMarkupString(MO mo) throws TemplateModelException {
+        String mc = mo.getMarkupContent();
+        if (mc != null) {
+            return mc;
+        }
+        
+        mc = escapePlainText(mo.getPlainTextContent());
+        mo.setMarkupContent(mc);
+        return mc;
+    }
+    
+    @Override
+    public final MO concat(MO mo1, MO mo2) throws TemplateModelException {
+        String pc1 = mo1.getPlainTextContent();
+        String mc1 = mo1.getMarkupContent();
+        String pc2 = mo2.getPlainTextContent();
+        String mc2 = mo2.getMarkupContent();
+        
+        String pc3 = pc1 != null && pc2 != null ? pc1 + pc2 : null;
+        String mc3 = mc1 != null && mc2 != null ? mc1 + mc2 : null;
+        if (pc3 != null || mc3 != null) {
+            return newTemplateMarkupOutputModel(pc3, mc3);
+        }
+        
+        if (pc1 != null) {
+            return newTemplateMarkupOutputModel(null, getMarkupString(mo1) + 
mc2);
+        } else {
+            return newTemplateMarkupOutputModel(null, mc1 + 
getMarkupString(mo2));
+        }
+    }
+    
+    @Override
+    public boolean isEmpty(MO mo) throws TemplateModelException {
+        String s = mo.getPlainTextContent();
+        if (s != null) {
+            return s.length() == 0;
+        }
+        return mo.getMarkupContent().length() == 0;
+    }
+    
+    @Override
+    public boolean isOutputFormatMixingAllowed() {
+        return false;
+    }
+    
+    @Override
+    public boolean isAutoEscapedByDefault() {
+        return true;
+    }
+
+    /**
+     * Creates a new {@link CommonTemplateMarkupOutputModel} that's bound to 
this {@link OutputFormat} instance.
+     */
+    protected abstract MO newTemplateMarkupOutputModel(String 
plainTextContent, String markupContent)
+            throws TemplateModelException;
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/CommonTemplateMarkupOutputModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/CommonTemplateMarkupOutputModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/CommonTemplateMarkupOutputModel.java
new file mode 100644
index 0000000..c6a7894
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/CommonTemplateMarkupOutputModel.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.outputformat;
+
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+
+/**
+ * Common superclass for implementing {@link TemplateMarkupOutputModel}-s that 
belong to a
+ * {@link CommonMarkupOutputFormat} subclass format.
+ * 
+ * <p>
+ * Thread-safe after proper publishing. Calculated fields (typically, the 
markup calculated from plain text) might will
+ * be re-calculated for multiple times if accessed from multiple threads (this 
only affects performance, not
+ * functionality).
+ * 
+ * @since 2.3.24
+ */
+public abstract class CommonTemplateMarkupOutputModel<MO extends 
CommonTemplateMarkupOutputModel<MO>>
+        implements TemplateMarkupOutputModel<MO> {
+
+    private final String plainTextContent;
+    private String markupContent;
+
+    /**
+     * A least one of the parameters must be non-{@code null}!
+     */
+    protected CommonTemplateMarkupOutputModel(String plainTextContent, String 
markupContent) {
+        this.plainTextContent = plainTextContent;
+        this.markupContent = markupContent;
+    }
+
+    @Override
+    public abstract CommonMarkupOutputFormat<MO> getOutputFormat();
+
+    /** Maybe {@code null}, but then {@link #getMarkupContent()} isn't {@code 
null}. */
+    final String getPlainTextContent() {
+        return plainTextContent;
+    }
+
+    /** Maybe {@code null}, but then {@link #getPlainTextContent()} isn't 
{@code null}. */
+    final String getMarkupContent() {
+        return markupContent;
+    }
+
+    /**
+     * Use only to set the value calculated from {@link 
#getPlainTextContent()}, when {@link #getMarkupContent()} was
+     * still {@code null}!
+     */
+    final void setMarkupContent(String markupContent) {
+        this.markupContent = markupContent;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java
new file mode 100644
index 0000000..aac7d54
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/MarkupOutputFormat.java
@@ -0,0 +1,135 @@
+/*
+ * 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.outputformat;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.TemplateHTMLOutputModel;
+
+/**
+ * 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 the
+ * {@link Configuration#getOutputFormat() outputFormat} configuration setting.)
+ * 
+ * <p>
+ * A {@link MarkupOutputFormat} 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#getAutoEscapingPolicy()
+     */
+    public abstract boolean isAutoEscapedByDefault();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java
new file mode 100644
index 0000000..8004ae2
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/OutputFormat.java
@@ -0,0 +1,86 @@
+/*
+ * 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.outputformat;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents an output format. If you need auto-escaping, see its subclass, 
{@link MarkupOutputFormat}.
+ * 
+ * @see Configuration#getOutputFormat()
+ * @see Configuration#getRegisteredCustomOutputFormats()
+ * @see MarkupOutputFormat
+ * 
+ * @since 2.3.24
+ */
+public abstract class OutputFormat {
+
+    /**
+     * The short name used to refer to this format (like in the {@code #ftl} 
header).
+     */
+    public abstract String getName();
+    
+    /**
+     * Returns the MIME type of the output format. This might comes handy when 
generating a HTTP response. {@code null}
+     * {@code null} if this output format doesn't clearly corresponds to a 
specific MIME type.
+     */
+    public abstract String getMimeType();
+
+    /**
+     * Tells if this output format allows inserting {@link 
TemplateMarkupOutputModel}-s of another output formats into
+     * it. If {@code true}, the foreign {@link TemplateMarkupOutputModel} will 
be inserted into the output as is (like
+     * if the surrounding output format was the same). This is usually a bad 
idea to allow, as such an event could
+     * indicate application bugs. If this method returns {@code false} 
(recommended), then FreeMarker will try to
+     * assimilate the inserted value by converting its format to this format, 
which will currently (2.3.24) cause
+     * exception, unless the inserted value is made by escaping plain text and 
the target format is non-escaping, in
+     * which case format conversion is trivially possible. (It's not 
impossible that conversions will be extended beyond
+     * this, if there will be demand for that.)
+     * 
+     * <p>
+     * {@code true} value is used by {@link UndefinedOutputFormat}.
+     */
+    public abstract boolean isOutputFormatMixingAllowed();
+
+    /**
+     * Returns the short description of this format, to be used in error 
messages.
+     * Override {@link #toStringExtraProperties()} to customize this.
+     */
+    @Override
+    public final String toString() {
+        String extras = toStringExtraProperties();
+        return getName() + "("
+                + "mimeType=" + _StringUtil.jQuote(getMimeType()) + ", "
+                + "class=" + _ClassUtil.getShortClassNameOfObject(this, true)
+                + (extras.length() != 0 ? ", " : "") + extras
+                + ")";
+    }
+    
+    /**
+     * Should be like {@code "foo=\"something\", bar=123"}; this will be 
inserted inside the parentheses in
+     * {@link #toString()}. Shouldn't return {@code null}; should return 
{@code ""} if there are no extra properties.  
+     */
+    protected String toStringExtraProperties() {
+        return "";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/UnregisteredOutputFormatException.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/UnregisteredOutputFormatException.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/UnregisteredOutputFormatException.java
new file mode 100644
index 0000000..86dbfa3
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/UnregisteredOutputFormatException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.outputformat;
+
+import org.apache.freemarker.core.Configuration;
+
+/**
+ * Thrown by {@link Configuration#getOutputFormat(String)}.
+ * 
+ * @since 2.3.24
+ */
+@SuppressWarnings("serial")
+public class UnregisteredOutputFormatException extends Exception {
+
+    public UnregisteredOutputFormatException(String message) {
+        this(message, null);
+    }
+    
+    public UnregisteredOutputFormatException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/CSSOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/CSSOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/CSSOutputFormat.java
new file mode 100644
index 0000000..6a03d54
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/CSSOutputFormat.java
@@ -0,0 +1,54 @@
+/*
+ * 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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Represents the CSS output format (MIME type "text/css", name "CSS"). This 
format doesn't support escaping.
+ * 
+ * @since 2.3.24
+ */
+public class CSSOutputFormat extends OutputFormat {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final CSSOutputFormat INSTANCE = new CSSOutputFormat();
+    
+    private CSSOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "CSS";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "text/css";
+    }
+
+    @Override
+    public boolean isOutputFormatMixingAllowed() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/CombinedMarkupOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/CombinedMarkupOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/CombinedMarkupOutputFormat.java
new file mode 100644
index 0000000..5239e3f
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/CombinedMarkupOutputFormat.java
@@ -0,0 +1,108 @@
+/*
+ * 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.outputformat.impl;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.outputformat.CommonMarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+
+/**
+ * Represents two markup formats nested into each other. For example, markdown 
nested into HTML.
+ * 
+ * @since 2.3.24
+ */
+public final class CombinedMarkupOutputFormat extends 
CommonMarkupOutputFormat<TemplateCombinedMarkupOutputModel> {
+
+    private final String name;
+    
+    private final MarkupOutputFormat outer;
+    private final MarkupOutputFormat inner;
+
+    /**
+     * Same as {@link #CombinedMarkupOutputFormat(String, MarkupOutputFormat, 
MarkupOutputFormat)} with {@code null} as
+     * the {@code name} parameter.
+     */
+    public CombinedMarkupOutputFormat(MarkupOutputFormat outer, 
MarkupOutputFormat inner) {
+        this(null, outer, inner);
+    }
+    
+    /**
+     * @param name
+     *            Maybe {@code null}, in which case it defaults to
+     *            <code>outer.getName() + "{" + inner.getName() + "}"</code>.
+     */
+    public CombinedMarkupOutputFormat(String name, MarkupOutputFormat outer, 
MarkupOutputFormat inner) {
+        this.name = name != null ? null : outer.getName() + "{" + 
inner.getName() + "}";
+        this.outer = outer;
+        this.inner = inner;
+    }
+    
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getMimeType() {
+        return outer.getMimeType();
+    }
+
+    @Override
+    public void output(String textToEsc, Writer out) throws IOException, 
TemplateModelException {
+        outer.output(inner.escapePlainText(textToEsc), out);
+    }
+
+    @Override
+    public String escapePlainText(String plainTextContent) throws 
TemplateModelException {
+        return outer.escapePlainText(inner.escapePlainText(plainTextContent));
+    }
+
+    @Override
+    public boolean isLegacyBuiltInBypassed(String builtInName) throws 
TemplateModelException {
+        return outer.isLegacyBuiltInBypassed(builtInName);
+    }
+
+    @Override
+    public boolean isAutoEscapedByDefault() {
+        return outer.isAutoEscapedByDefault();
+    }
+    
+    @Override
+    public boolean isOutputFormatMixingAllowed() {
+        return outer.isOutputFormatMixingAllowed();
+    }
+
+    public MarkupOutputFormat getOuterOutputFormat() {
+        return outer;
+    }
+
+    public MarkupOutputFormat getInnerOutputFormat() {
+        return inner;
+    }
+
+    @Override
+    protected TemplateCombinedMarkupOutputModel newTemplateMarkupOutputModel(
+            String plainTextContent, String markupContent) {
+        return new TemplateCombinedMarkupOutputModel(plainTextContent, 
markupContent, this);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/HTMLOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/HTMLOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/HTMLOutputFormat.java
new file mode 100644
index 0000000..0cebf64
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/HTMLOutputFormat.java
@@ -0,0 +1,77 @@
+/*
+ * 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.outputformat.impl;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.outputformat.CommonMarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents the HTML output format (MIME type "text/html", name "HTML"). 
This format escapes by default (via
+ * {@link _StringUtil#XHTMLEnc(String)}). The {@code ?html}, {@code ?xhtml} 
and {@code ?xml} built-ins silently bypass
+ * template output values of the type produced by this output format ({@link 
TemplateHTMLOutputModel}).
+ * 
+ * @since 2.3.24
+ */
+public final class HTMLOutputFormat extends 
CommonMarkupOutputFormat<TemplateHTMLOutputModel> {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final HTMLOutputFormat INSTANCE = new HTMLOutputFormat();
+    
+    private HTMLOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "HTML";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "text/html";
+    }
+
+    @Override
+    public void output(String textToEsc, Writer out) throws IOException, 
TemplateModelException {
+        _StringUtil.XHTMLEnc(textToEsc, out);
+    }
+
+    @Override
+    public String escapePlainText(String plainTextContent) {
+        return _StringUtil.XHTMLEnc(plainTextContent);
+    }
+
+    @Override
+    public boolean isLegacyBuiltInBypassed(String builtInName) {
+        return builtInName.equals("html") || builtInName.equals("xml") || 
builtInName.equals("xhtml");
+    }
+
+    @Override
+    protected TemplateHTMLOutputModel newTemplateMarkupOutputModel(String 
plainTextContent, String markupContent) {
+        return new TemplateHTMLOutputModel(plainTextContent, markupContent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/JSONOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/JSONOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/JSONOutputFormat.java
new file mode 100644
index 0000000..c420e69
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/JSONOutputFormat.java
@@ -0,0 +1,54 @@
+/*
+ * 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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Represents the JSON output format (MIME type "application/json", name 
"JSON"). This format doesn't support escaping.
+ * 
+ * @since 2.3.24
+ */
+public class JSONOutputFormat extends OutputFormat {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final JSONOutputFormat INSTANCE = new JSONOutputFormat();
+    
+    private JSONOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "JSON";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "application/json";
+    }
+
+    @Override
+    public boolean isOutputFormatMixingAllowed() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/JavaScriptOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/JavaScriptOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/JavaScriptOutputFormat.java
new file mode 100644
index 0000000..b2e8176
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/JavaScriptOutputFormat.java
@@ -0,0 +1,55 @@
+/*
+ * 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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Represents the JavaScript output format (MIME type 
"application/javascript", name "JavaScript"). This format doesn't
+ * support escaping.
+ * 
+ * @since 2.3.24
+ */
+public class JavaScriptOutputFormat extends OutputFormat {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final JavaScriptOutputFormat INSTANCE = new 
JavaScriptOutputFormat();
+    
+    private JavaScriptOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "JavaScript";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "application/javascript";
+    }
+
+    @Override
+    public boolean isOutputFormatMixingAllowed() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/PlainTextOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/PlainTextOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/PlainTextOutputFormat.java
new file mode 100644
index 0000000..13cddc8
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/PlainTextOutputFormat.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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Represents the plain text output format (MIME type "text/plain", name 
"plainText"). This format doesn't support
+ * escaping. This format doesn't allow mixing in template output values of 
other output formats.
+ * 
+ * <p>
+ * The main difference from {@link UndefinedOutputFormat} is that this format 
doesn't allow inserting values of another
+ * output format into itself (unless they can be converted to plain text), 
while {@link UndefinedOutputFormat} would
+ * just insert the foreign "markup" as is. Also, this format has 
{"text/plain"} MIME type, while
+ * {@link UndefinedOutputFormat} has {@code null}.
+ * 
+ * @since 2.3.24
+ */
+public final class PlainTextOutputFormat extends OutputFormat {
+
+    public static final PlainTextOutputFormat INSTANCE = new 
PlainTextOutputFormat();
+    
+    private PlainTextOutputFormat() {
+        // Only to decrease visibility
+    }
+
+    @Override
+    public boolean isOutputFormatMixingAllowed() {
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        return "plainText";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "text/plain";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/RTFOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/RTFOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/RTFOutputFormat.java
new file mode 100644
index 0000000..be38b89
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/RTFOutputFormat.java
@@ -0,0 +1,77 @@
+/*
+ * 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.outputformat.impl;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.outputformat.CommonMarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents the Rich Text Format output format (MIME type "application/rtf", 
name "RTF"). This format escapes by
+ * default (via {@link _StringUtil#RTFEnc(String)}). The {@code ?rtf} built-in 
silently bypasses template output values
+ * of the type produced by this output format ({@link TemplateRTFOutputModel}).
+ * 
+ * @since 2.3.24
+ */
+public final class RTFOutputFormat extends 
CommonMarkupOutputFormat<TemplateRTFOutputModel> {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final RTFOutputFormat INSTANCE = new RTFOutputFormat();
+    
+    private RTFOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "RTF";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "application/rtf";
+    }
+
+    @Override
+    public void output(String textToEsc, Writer out) throws IOException, 
TemplateModelException {
+        _StringUtil.RTFEnc(textToEsc, out);
+    }
+
+    @Override
+    public String escapePlainText(String plainTextContent) {
+        return _StringUtil.RTFEnc(plainTextContent);
+    }
+
+    @Override
+    public boolean isLegacyBuiltInBypassed(String builtInName) {
+        return builtInName.equals("rtf");
+    }
+
+    @Override
+    protected TemplateRTFOutputModel newTemplateMarkupOutputModel(String 
plainTextContent, String markupContent) {
+        return new TemplateRTFOutputModel(plainTextContent, markupContent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateCombinedMarkupOutputModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateCombinedMarkupOutputModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateCombinedMarkupOutputModel.java
new file mode 100644
index 0000000..345a197
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateCombinedMarkupOutputModel.java
@@ -0,0 +1,52 @@
+/*
+ * 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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.CommonTemplateMarkupOutputModel;
+
+/**
+ * Stores combined markup to be printed; used with {@link 
CombinedMarkupOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateCombinedMarkupOutputModel
+        extends 
CommonTemplateMarkupOutputModel<TemplateCombinedMarkupOutputModel> {
+    
+    private final CombinedMarkupOutputFormat outputFormat;
+    
+    /**
+     * See {@link 
CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, 
String)}.
+     * 
+     * @param outputFormat
+     *            The {@link CombinedMarkupOutputFormat} format this value is 
bound to. Because
+     *            {@link CombinedMarkupOutputFormat} has no singleton, we have 
to pass it in, unlike with most other
+     *            {@link CommonTemplateMarkupOutputModel}-s.
+     */
+    TemplateCombinedMarkupOutputModel(String plainTextContent, String 
markupContent,
+            CombinedMarkupOutputFormat outputFormat) {
+        super(plainTextContent, markupContent);
+        this.outputFormat = outputFormat; 
+    }
+
+    @Override
+    public CombinedMarkupOutputFormat getOutputFormat() {
+        return outputFormat;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateHTMLOutputModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateHTMLOutputModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateHTMLOutputModel.java
new file mode 100644
index 0000000..7bff952
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateHTMLOutputModel.java
@@ -0,0 +1,42 @@
+/*
+ * 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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.CommonTemplateMarkupOutputModel;
+
+/**
+ * Stores HTML markup to be printed; used with {@link HTMLOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateHTMLOutputModel extends 
CommonTemplateMarkupOutputModel<TemplateHTMLOutputModel> {
+    
+    /**
+     * See {@link 
CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, 
String)}.
+     */
+    TemplateHTMLOutputModel(String plainTextContent, String markupContent) {
+        super(plainTextContent, markupContent);
+    }
+
+    @Override
+    public HTMLOutputFormat getOutputFormat() {
+        return HTMLOutputFormat.INSTANCE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateRTFOutputModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateRTFOutputModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateRTFOutputModel.java
new file mode 100644
index 0000000..f01ff07
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateRTFOutputModel.java
@@ -0,0 +1,42 @@
+/*
+ * 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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.CommonTemplateMarkupOutputModel;
+
+/**
+ * Stores RTF markup to be printed; used with {@link RTFOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateRTFOutputModel extends 
CommonTemplateMarkupOutputModel<TemplateRTFOutputModel> {
+    
+    /**
+     * See {@link 
CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, 
String)}.
+     */
+    TemplateRTFOutputModel(String plainTextContent, String markupContent) {
+        super(plainTextContent, markupContent);
+    }
+
+    @Override
+    public RTFOutputFormat getOutputFormat() {
+        return RTFOutputFormat.INSTANCE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateXHTMLOutputModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateXHTMLOutputModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateXHTMLOutputModel.java
new file mode 100644
index 0000000..f0fbf1d
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateXHTMLOutputModel.java
@@ -0,0 +1,42 @@
+/*
+ * 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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.CommonTemplateMarkupOutputModel;
+
+/**
+ * Stores HTML markup to be printed; used with {@link HTMLOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateXHTMLOutputModel extends 
CommonTemplateMarkupOutputModel<TemplateXHTMLOutputModel> {
+    
+    /**
+     * See {@link 
CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, 
String)}.
+     */
+    TemplateXHTMLOutputModel(String plainTextContent, String markupContent) {
+        super(plainTextContent, markupContent);
+    }
+
+    @Override
+    public XHTMLOutputFormat getOutputFormat() {
+        return XHTMLOutputFormat.INSTANCE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateXMLOutputModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateXMLOutputModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateXMLOutputModel.java
new file mode 100644
index 0000000..62e7867
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/TemplateXMLOutputModel.java
@@ -0,0 +1,42 @@
+/*
+ * 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.outputformat.impl;
+
+import org.apache.freemarker.core.outputformat.CommonTemplateMarkupOutputModel;
+
+/**
+ * Stores XML markup to be printed; used with {@link XMLOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateXMLOutputModel extends 
CommonTemplateMarkupOutputModel<TemplateXMLOutputModel> {
+    
+    /**
+     * See {@link 
CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, 
String)}.
+     */
+    TemplateXMLOutputModel(String plainTextContent, String markupContent) {
+        super(plainTextContent, markupContent);
+    }
+
+    @Override
+    public XMLOutputFormat getOutputFormat() {
+        return XMLOutputFormat.INSTANCE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.java
new file mode 100644
index 0000000..b5412e2
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/UndefinedOutputFormat.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.outputformat.impl;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+
+/**
+ * Represents the output format used when the template output format is 
undecided. This is the default output format if
+ * FreeMarker can't select anything more specific (see {@link 
Configuration#getTemplateConfigurations()}). This format
+ * doesn't support auto-escaping ({@link 
Configuration#getAutoEscapingPolicy()}). It will print
+ * {@link TemplateMarkupOutputModel}-s as is (doesn't try to convert them).
+ * 
+ * @see PlainTextOutputFormat
+ * 
+ * @since 2.3.24
+ */
+public final class UndefinedOutputFormat extends OutputFormat {
+
+    public static final UndefinedOutputFormat INSTANCE = new 
UndefinedOutputFormat();
+    
+    private UndefinedOutputFormat() {
+        // Only to decrease visibility
+    }
+
+    @Override
+    public boolean isOutputFormatMixingAllowed() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "undefined";
+    }
+
+    @Override
+    public String getMimeType() {
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/XHTMLOutputFormat.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/XHTMLOutputFormat.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/XHTMLOutputFormat.java
new file mode 100644
index 0000000..4334ba3
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/outputformat/impl/XHTMLOutputFormat.java
@@ -0,0 +1,77 @@
+/*
+ * 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.outputformat.impl;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.outputformat.CommonMarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents the XML output format (MIME type "application/xhtml+xml", name 
"XHTML"). This format escapes by default
+ * (via {@link _StringUtil#XHTMLEnc(String)}). The {@code ?xml} built-in 
silently bypasses template output values of the
+ * type produced by this output format ({@link TemplateXHTMLOutputModel}).
+ * 
+ * @since 2.3.24
+ */
+public final class XHTMLOutputFormat extends 
CommonMarkupOutputFormat<TemplateXHTMLOutputModel> {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final XHTMLOutputFormat INSTANCE = new XHTMLOutputFormat();
+    
+    private XHTMLOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "XHTML";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "application/xhtml+xml";
+    }
+
+    @Override
+    public void output(String textToEsc, Writer out) throws IOException, 
TemplateModelException {
+        _StringUtil.XHTMLEnc(textToEsc, out);
+    }
+
+    @Override
+    public String escapePlainText(String plainTextContent) {
+        return _StringUtil.XHTMLEnc(plainTextContent);
+    }
+
+    @Override
+    public boolean isLegacyBuiltInBypassed(String builtInName) {
+        return builtInName.equals("html") || builtInName.equals("xml") || 
builtInName.equals("xhtml");
+    }
+
+    @Override
+    protected TemplateXHTMLOutputModel newTemplateMarkupOutputModel(String 
plainTextContent, String markupContent) {
+        return new TemplateXHTMLOutputModel(plainTextContent, markupContent);
+    }
+
+}


Reply via email to