freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [30/51] [partial] incubator-freemarker git commit: Migrated from Ant to Gradle, and modularized the project. This is an incomplete migration; there are some TODO-s in the build scripts, and release related tasks are still missing. What works: Building th
Date Sun, 14 May 2017 10:53:13 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java
new file mode 100644
index 0000000..c664d01
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Note yet public; will change in 2.4 (as it has to process {@code UnboundTemplate}-s).
+ */
+abstract class TemplatePostProcessor {
+
+    public abstract void postProcess(Template e) throws TemplatePostProcessorException;
+    
+    // TODO: getPriority, getPhase, getMustBeBefore, getMustBeAfter
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java
new file mode 100644
index 0000000..cebaf36
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Not yet public; subject to change.
+ */
+class TemplatePostProcessorException extends Exception {
+
+    public TemplatePostProcessorException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public TemplatePostProcessorException(String message) {
+        super(message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
new file mode 100644
index 0000000..d05ba08
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
@@ -0,0 +1,140 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateDirectiveBody;
+
+/**
+ * Not yet public; subject to change.
+ * 
+ * <p>
+ * Known compatibility risks when using this post-processor:
+ * <ul>
+ * <li>{@link TemplateDateModel}-s that care to explicitly check if their nested content is {@code null} might start to
+ *   complain that you have specified a body despite that the directive doesn't support that. Directives should use
+ *   {@link NestedContentNotSupportedException#check(TemplateDirectiveBody)} instead of a simple
+ *   {@code null}-check to avoid this problem.</li>
+ * <li>
+ *   Software that uses {@link DirectiveCallPlace#isNestedOutputCacheable()} will always get {@code false}, because
+ *   interruption checks ({@link ASTThreadInterruptionCheck} elements) are, obviously, not cacheable. This should only
+ *   impact the performance.
+ * <li>
+ *   Software that investigates the AST will see the injected {@link ASTThreadInterruptionCheck} elements. As of this
+ *   writing the AST API-s aren't published, also such software need to be able to deal with new kind of elements
+ *   anyway, so this shouldn't be a problem.
+ * </ul>
+ */
+class ThreadInterruptionSupportTemplatePostProcessor extends TemplatePostProcessor {
+
+    @Override
+    public void postProcess(Template t) throws TemplatePostProcessorException {
+        final ASTElement te = t.getRootASTNode();
+        addInterruptionChecks(te);
+    }
+
+    private void addInterruptionChecks(final ASTElement te) throws TemplatePostProcessorException {
+        if (te == null) {
+            return;
+        }
+        
+        final int childCount = te.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            addInterruptionChecks(te.getChild(i));
+        }
+        
+        if (te.isNestedBlockRepeater()) {
+            try {
+                te.addChild(0, new ASTThreadInterruptionCheck(te));
+            } catch (ParseException e) {
+                throw new TemplatePostProcessorException("Unexpected error; see cause", e);
+            }
+        }
+    }
+
+    /**
+     * AST directive-like node: Checks if the current thread's "interrupted" flag is set, and throws
+     * {@link TemplateProcessingThreadInterruptedException} if it is. We inject this to some points into the AST.
+     */
+    static class ASTThreadInterruptionCheck extends ASTElement {
+        
+        private ASTThreadInterruptionCheck(ASTElement te) throws ParseException {
+            setLocation(te.getTemplate(), te.beginColumn, te.beginLine, te.beginColumn, te.beginLine);
+        }
+
+        @Override
+        ASTElement[] accept(Environment env) throws TemplateException, IOException {
+            // As the API doesn't allow throwing InterruptedException here (nor anywhere else, most importantly,
+            // Template.process can't throw it), we must not clear the "interrupted" flag of the thread.
+            if (Thread.currentThread().isInterrupted()) {
+                throw new TemplateProcessingThreadInterruptedException();
+            }
+            return null;
+        }
+
+        @Override
+        protected String dump(boolean canonical) {
+            return canonical ? "" : "<#--" + getNodeTypeSymbol() + "--#>";
+        }
+
+        @Override
+        String getNodeTypeSymbol() {
+            return "##threadInterruptionCheck";
+        }
+
+        @Override
+        int getParameterCount() {
+            return 0;
+        }
+
+        @Override
+        Object getParameterValue(int idx) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        @Override
+        ParameterRole getParameterRole(int idx) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        @Override
+        boolean isNestedBlockRepeater() {
+            return false;
+        }
+        
+    }
+    
+    /**
+     * Indicates that the template processing thread's "interrupted" flag was found to be set.
+     * 
+     * <p>ATTENTION: This is used by https://github.com/kenshoo/freemarker-online. Don't break backward
+     * compatibility without updating that project too! 
+     */
+    static class TemplateProcessingThreadInterruptedException extends RuntimeException {
+        
+        TemplateProcessingThreadInterruptedException() {
+            super("Template processing thread \"interrupted\" flag was set.");
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TokenMgrError.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TokenMgrError.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TokenMgrError.java
new file mode 100644
index 0000000..4587358
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TokenMgrError.java
@@ -0,0 +1,249 @@
+/*
+ * 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;
+
+/**
+ * Exception thrown on lower (lexical) level parsing errors. Shouldn't reach normal FreeMarker users, as FreeMarker
+ * usually catches this and wraps it into a {@link ParseException}.
+ * 
+ * This is a modified version of file generated by JavaCC from FTL.jj.
+ * You can modify this class to customize the error reporting mechanisms so long as the public interface
+ * remains compatible with the original.
+ * 
+ * @see ParseException
+ */
+class TokenMgrError extends Error {
+   /*
+    * Ordinals for various reasons why an Error of this type can be thrown.
+    */
+
+   /**
+    * Lexical error occurred.
+    */
+   static final int LEXICAL_ERROR = 0;
+
+   /**
+    * An attempt was made to invoke a second instance of a static token manager.
+    */
+   static final int STATIC_LEXER_ERROR = 1;
+
+   /**
+    * Tried to change to an invalid lexical state.
+    */
+   static final int INVALID_LEXICAL_STATE = 2;
+
+   /**
+    * Detected (and bailed out of) an infinite loop in the token manager.
+    */
+   static final int LOOP_DETECTED = 3;
+
+   /**
+    * Indicates the reason why the exception is thrown. It will have
+    * one of the above 4 values.
+    */
+   int errorCode;
+
+   private String detail;
+   private Integer lineNumber, columnNumber;
+   private Integer endLineNumber, endColumnNumber;
+
+   /**
+    * Replaces unprintable characters by their espaced (or unicode escaped)
+    * equivalents in the given string
+    */
+   protected static String addEscapes(String str) {
+      StringBuilder retval = new StringBuilder();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+   /**
+    * Returns a detailed message for the Error when it's thrown by the
+    * token manager to indicate a lexical error.
+    * Parameters : 
+    *    EOFSeen     : indicates if EOF caused the lexicl error
+    *    curLexState : lexical state in which this error occurred
+    *    errorLine   : line number when the error occurred
+    *    errorColumn : column number when the error occurred
+    *    errorAfter  : prefix that was seen before this error occurred
+    *    curchar     : the offending character
+    * Note: You can customize the lexical error message by modifying this method.
+    */
+   protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+      return("Lexical error: encountered " +
+           (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int) curChar + "), ") +
+           "after \"" + addEscapes(errorAfter) + "\".");
+   }
+
+   /**
+    * You can also modify the body of this method to customize your error messages.
+    * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+    * of end-users concern, so you can return something like : 
+    *
+    *     "Internal Error : Please file a bug report .... "
+    *
+    * from this method for such cases in the release version of your parser.
+    */
+   @Override
+public String getMessage() {
+      return super.getMessage();
+   }
+
+   /*
+    * Constructors of various flavors follow.
+    */
+
+   public TokenMgrError() {
+   }
+
+   public TokenMgrError(String detail, int reason) {
+      super(detail);  // the "detail" must not contain location information, the "message" might does
+      this.detail = detail;
+      errorCode = reason;
+   }
+   
+   /**
+    * @since 2.3.21
+    */
+   public TokenMgrError(String detail, int reason,
+           int errorLine, int errorColumn,
+           int endLineNumber, int endColumnNumber) {
+       super(detail);  // the "detail" must not contain location information, the "message" might does
+       this.detail = detail;
+       errorCode = reason;
+
+      lineNumber = Integer.valueOf(errorLine);  // In J2SE there was no Integer.valueOf(int)
+      columnNumber = Integer.valueOf(errorColumn);
+       this.endLineNumber = Integer.valueOf(endLineNumber); 
+       this.endColumnNumber = Integer.valueOf(endColumnNumber); 
+    }
+
+   /**
+    * Overload for JavaCC 6 compatibility.
+    * 
+    * @since 2.3.24
+    */
+   TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, int curChar, int reason) {
+       this(EOFSeen, lexState, errorLine, errorColumn, errorAfter, (char) curChar, reason);
+   }
+   
+   public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+      this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+
+      lineNumber = Integer.valueOf(errorLine);  // In J2SE there was no Integer.valueOf(int)
+      columnNumber = Integer.valueOf(errorColumn);
+      // We blame the single character that can't be the start of a legal token: 
+      endLineNumber = lineNumber;
+      endColumnNumber = columnNumber;
+   }
+
+   /**
+    * 1-based line number of the unexpected character(s).
+    * 
+    * @since 2.3.20
+    */
+   public Integer getLineNumber() {
+      return lineNumber;
+   }
+    
+   /**
+    * 1-based column number of the unexpected character(s).
+    * 
+    * @since 2.3.20
+    */
+   public Integer getColumnNumber() {
+      return columnNumber;
+   }
+   
+   /**
+    * Returns the 1-based line at which the last character of the wrong section is. This will be usually (but not
+    * always) the same as {@link #getLineNumber()} because the lexer can only point to the single character that
+    * doesn't match any patterns.
+    * 
+    * @since 2.3.21
+    */
+   public Integer getEndLineNumber() {
+      return endLineNumber;
+   }
+
+   /**
+    * Returns the 1-based column at which the last character of the wrong section is. This will be usually (but not
+    * always) the same as {@link #getColumnNumber()} because the lexer can only point to the single character that
+    * doesn't match any patterns.
+    * 
+    * @since 2.3.21
+    */
+   public Integer getEndColumnNumber() {
+      return endColumnNumber;
+   }
+
+   public String getDetail() {
+       return detail;
+   }
+
+   public ParseException toParseException(Template template) {
+       return new ParseException(getDetail(),
+               template,
+               getLineNumber() != null ? getLineNumber().intValue() : 0,
+               getColumnNumber() != null ? getColumnNumber().intValue() : 0,
+               getEndLineNumber() != null ? getEndLineNumber().intValue() : 0,
+               getEndColumnNumber() != null ? getEndColumnNumber().intValue() : 0);
+   }
+   
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java
new file mode 100644
index 0000000..8b253a2
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TopLevelConfiguration.java
@@ -0,0 +1,194 @@
+/*
+ * 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.Map;
+
+import org.apache.freemarker.core.templateresolver.CacheStorage;
+import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy;
+import org.apache.freemarker.core.templateresolver.TemplateNameFormat;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
+import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
+import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.SoftCacheStorage;
+
+/**
+ * Implemented by FreeMarker core classes (not by you) that provide {@link Configuration}-level settings. <b>New
+ * methods may be added any time in future FreeMarker versions, so don't try to implement this interface yourself!</b>
+ *
+ * @see ParsingAndProcessingConfiguration
+ */
+public interface TopLevelConfiguration extends ParsingAndProcessingConfiguration {
+
+    /**
+     * The {@link TemplateLoader} that is used to look up and load templates.
+     * By providing your own {@link TemplateLoader} implementation, you can load templates from whatever kind of
+     * storages, like from relational databases, NoSQL-storages, etc.
+     *
+     * <p>You can chain several {@link TemplateLoader}-s together with {@link MultiTemplateLoader}.
+     *
+     * <p>Default value: You should always set the template loader instead of relying on the default value.
+     * (But if you still care what it is, before "incompatible improvements" 2.3.21 it's a {@link FileTemplateLoader}
+     * that uses the current directory as its root; as it's hard tell what that directory will be, it's not very useful
+     * and dangerous. Starting with "incompatible improvements" 2.3.21 the default is {@code null}.)
+     */
+    TemplateLoader getTemplateLoader();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateLoaderSet();
+
+    /**
+     * The {@link TemplateLookupStrategy} that is used to look up templates based on the requested name, locale and
+     * custom lookup condition. Its default is {@link DefaultTemplateLookupStrategy#INSTANCE}.
+     */
+    TemplateLookupStrategy getTemplateLookupStrategy();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateLookupStrategySet();
+
+    /**
+     * The template name format used; see {@link TemplateNameFormat}. The default is
+     * {@link DefaultTemplateNameFormatFM2#INSTANCE}, while the recommended value for new projects is
+     * {@link DefaultTemplateNameFormat#INSTANCE}.
+     */
+    TemplateNameFormat getTemplateNameFormat();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateNameFormatSet();
+
+    /**
+     * The {@link TemplateConfigurationFactory} that will configure individual templates where their settings differ
+     * from those coming from the common {@link Configuration} object. A typical use case for that is specifying the
+     * {@link #getOutputFormat() outputFormat} or {@link #getSourceEncoding() sourceEncoding} for templates based on
+     * their file extension or parent directory.
+     * <p>
+     * Note that the settings suggested by standard file extensions are stronger than that you set here. See
+     * {@link #getRecognizeStandardFileExtensions()} for more information about standard file extensions.
+     * <p>
+     * See "Template configurations" in the FreeMarker Manual for examples.
+     */
+    TemplateConfigurationFactory getTemplateConfigurations();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateConfigurationsSet();
+
+    /**
+     * The map-like object used for caching templates to avoid repeated loading and parsing of the template "files".
+     * Its {@link Configuration}-level default is a {@link SoftCacheStorage}.
+     */
+    CacheStorage getCacheStorage();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isCacheStorageSet();
+
+    /**
+     * The time in milliseconds that must elapse before checking whether there is a newer version of a template
+     * "file" than the cached one. Defaults to 5000 ms.
+     */
+    long getTemplateUpdateDelayMilliseconds();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isTemplateUpdateDelayMillisecondsSet();
+
+    /**
+     * Returns the value of the "incompatible improvements" setting; this is the FreeMarker version number where the
+     * not 100% backward compatible bug fixes and improvements that you want to enable were already implemented. In
+     * new projects you should set this to the FreeMarker version that you are actually using. In older projects it's
+     * also usually better to keep this high, however you better check the changes activated (find them below), at
+     * least if not only the 3rd version number (the micro version) of {@code incompatibleImprovements} is increased.
+     * Generally, as far as you only increase the last version number of this setting, the changes are always low
+     * risk.
+     * <p>
+     * Bugfixes and improvements that are fully backward compatible, also those that are important security fixes,
+     * are enabled regardless of the incompatible improvements setting.
+     * <p>
+     * An important consequence of setting this setting is that now your application will check if the stated minimum
+     * FreeMarker version requirement is met. Like if you set this setting to 3.0.1, but accidentally the
+     * application is deployed with FreeMarker 3.0.0, then FreeMarker will fail, telling that a higher version is
+     * required. After all, the fixes/improvements you have requested aren't available on a lower version.
+     * <p>
+     * Note that as FreeMarker's minor (2nd) or major (1st) version number increments, it's possible that emulating
+     * some of the old bugs will become unsupported, that is, even if you set this setting to a low value, it
+     * silently wont bring back the old behavior anymore. Information about that will be present here.
+     *
+     * <p>Currently the effects of this setting are:
+     * <ul>
+     *   <li><p>
+     *     3.0.0: This is the lowest supported value in FreeMarker 3.
+     *   </li>
+     * </ul>
+     *
+     * @return Never {@code null}.
+     */
+    @Override
+    Version getIncompatibleImprovements();
+
+    /**
+     * Whether localized template lookup is enabled. Enabled by default.
+     *
+     * <p>
+     * With the default {@link TemplateLookupStrategy}, localized lookup works like this: Let's say your locale setting
+     * is {@code Locale("en", "AU")}, and you call {@link Configuration#getTemplate(String) cfg.getTemplate("foo.ftl")}.
+     * Then FreeMarker will look for the template under these names, stopping at the first that exists:
+     * {@code "foo_en_AU.ftl"}, {@code "foo_en.ftl"}, {@code "foo.ftl"}. See the description of the default value at
+     * {@link #getTemplateLookupStrategy()} for a more details. If you need to generate different
+     * template names, set your own a {@link TemplateLookupStrategy} implementation as the value of the
+     * {@link #getTemplateLookupStrategy() templateLookupStrategy} setting.
+     */
+    boolean getLocalizedLookup();
+
+    /**
+     * Tells if this setting was explicitly set (otherwise its value will be the default value).
+     */
+    boolean isLocalizedLookupSet();
+
+    /**
+     * Shared variables are variables that are visible as top-level variables for all templates, except where the data
+     * model contains a variable with the same name (which then shadows the shared variable).
+     *
+     * @return Not {@code null}; the {@link Map} is possibly mutable in builders, but immutable in
+     *      {@link Configuration}.
+     *
+     * @see Configuration.Builder#setSharedVariables(Map)
+     */
+    Map<String, Object> getSharedVariables();
+
+    /**
+     * Tells if this setting was explicitly set (if not, the default value of the setting will be used).
+     */
+    boolean isSharedVariablesSet();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java
new file mode 100644
index 0000000..0f9a013
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+/**
+ * The type of a value differs from what was expected.
+ * 
+ * @since 2.3.20
+ */
+public class UnexpectedTypeException extends TemplateException {
+    
+    public UnexpectedTypeException(Environment env, String description) {
+        super(description, env);
+    }
+
+    UnexpectedTypeException(Environment env, _ErrorDescriptionBuilder description) {
+        super(null, env, null, description);
+    }
+
+    UnexpectedTypeException(
+            ASTExpression blamed, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, Environment env)
+            throws InvalidReferenceException {
+        super(null, env, blamed, newDesciptionBuilder(blamed, null, model, expectedTypesDesc, expectedTypes, env));
+    }
+
+    UnexpectedTypeException(
+            ASTExpression blamed, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, String tip,
+            Environment env)
+            throws InvalidReferenceException {
+        super(null, env, blamed, newDesciptionBuilder(blamed, null, model, expectedTypesDesc, expectedTypes, env)
+                .tip(tip));
+    }
+
+    UnexpectedTypeException(
+            ASTExpression blamed, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, Object[] tips,
+            Environment env)
+            throws InvalidReferenceException {
+        super(null, env, blamed, newDesciptionBuilder(blamed, null, model, expectedTypesDesc, expectedTypes, env)
+                .tips(tips));
+    }
+
+    UnexpectedTypeException(
+            String blamedAssignmentTargetVarName, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes,
+            Object[] tips,
+            Environment env)
+            throws InvalidReferenceException {
+        super(null, env, null, newDesciptionBuilder(
+                null, blamedAssignmentTargetVarName, model, expectedTypesDesc, expectedTypes, env).tips(tips));
+    }
+    
+    /**
+     * @param blamedAssignmentTargetVarName
+     *            Used for assignments that use {@code +=} and such, in which case the {@code blamed} expression
+     *            parameter will be null {@code null} and this parameter will be non-{null}.
+     */
+    private static _ErrorDescriptionBuilder newDesciptionBuilder(
+            ASTExpression blamed, String blamedAssignmentTargetVarName,
+            TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, Environment env)
+            throws InvalidReferenceException {
+        if (model == null) throw InvalidReferenceException.getInstance(blamed, env);
+
+        _ErrorDescriptionBuilder errorDescBuilder = new _ErrorDescriptionBuilder(
+                unexpectedTypeErrorDescription(expectedTypesDesc, blamed, blamedAssignmentTargetVarName, model))
+                .blame(blamed).showBlamer(true);
+        if (model instanceof _UnexpectedTypeErrorExplainerTemplateModel) {
+            Object[] tip = ((_UnexpectedTypeErrorExplainerTemplateModel) model).explainTypeError(expectedTypes);
+            if (tip != null) {
+                errorDescBuilder.tip(tip);
+            }
+        }
+        return errorDescBuilder;
+    }
+
+    private static Object[] unexpectedTypeErrorDescription(
+            String expectedTypesDesc,
+            ASTExpression blamed, String blamedAssignmentTargetVarName,
+            TemplateModel model) {
+        return new Object[] {
+                "Expected ", new _DelayedAOrAn(expectedTypesDesc), ", but ",
+                (blamedAssignmentTargetVarName == null
+                        ? blamed != null ? "this" : "the expression"
+                        : new Object[] {
+                                "assignment target variable ",
+                                new _DelayedJQuote(blamedAssignmentTargetVarName) }), 
+                " has evaluated to ",
+                new _DelayedAOrAn(new _DelayedFTLTypeDescription(model)),
+                (blamedAssignmentTargetVarName == null ? ":" : ".")};
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java
new file mode 100644
index 0000000..a4e562c
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/UnknownConfigurationSettingException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.Configuration.ExtendableBuilder;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Thrown by {@link ExtendableBuilder#setSetting(String, String)} if the setting name was not recognized.
+ */
+@SuppressWarnings("serial")
+public class UnknownConfigurationSettingException extends ConfigurationException {
+
+    UnknownConfigurationSettingException(String name, String correctedName) {
+        super("Unknown FreeMarker configuration setting: " + _StringUtil.jQuote(name)
+                + (correctedName == null ? "" : ". You may meant: " + _StringUtil.jQuote(correctedName)));
+    }
+
+    UnknownConfigurationSettingException(String name, Version removedInVersion) {
+        super("Unknown FreeMarker configuration setting: " + _StringUtil.jQuote(name)
+                + (removedInVersion == null ? "" : ". This setting was removed in version " + removedInVersion));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java
new file mode 100644
index 0000000..273f9f7
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Version.java
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Represents a version number plus the further qualifiers and build info. This is
+ * mostly used for representing a FreeMarker version number, but should also be able
+ * to parse the version strings of 3rd party libraries.
+ * 
+ * @see Configuration#getVersion()
+ * 
+ * @since 2.3.20
+ */
+public final class Version implements Serializable {
+    
+    private final int major;
+    private final int minor;
+    private final int micro;
+    private final String extraInfo;
+    private final String originalStringValue;
+    
+    private final Boolean gaeCompliant;
+    private final Date buildDate;
+    
+    private final int intValue;
+    private volatile String calculatedStringValue;  // not final because it's calculated on demand
+    private int hashCode;  // not final because it's calculated on demand
+
+    /**
+     * @throws IllegalArgumentException if the version string is malformed
+     */
+    public Version(String stringValue) {
+        this(stringValue, null, null);
+    }
+    
+    /**
+     * @throws IllegalArgumentException if the version string is malformed
+     */
+    public Version(String stringValue, Boolean gaeCompliant, Date buildDate) {
+        stringValue = stringValue.trim();
+        originalStringValue = stringValue; 
+        
+        int[] parts = new int[3];
+        String extraInfoTmp = null;
+        {
+            int partIdx = 0;
+            for (int i = 0; i < stringValue.length(); i++) {
+                char c = stringValue.charAt(i);
+                if (isNumber(c)) {
+                    parts[partIdx] = parts[partIdx] * 10 + (c - '0');
+                } else {
+                    if (i == 0) {
+                        throw new IllegalArgumentException(
+                                "The version number string " + _StringUtil.jQuote(stringValue)
+                                + " doesn't start with a number.");
+                    }
+                    if (c == '.') {
+                        char nextC = i + 1 >= stringValue.length() ? 0 : stringValue.charAt(i + 1);
+                        if (nextC == '.') {
+                            throw new IllegalArgumentException(
+                                    "The version number string " + _StringUtil.jQuote(stringValue)
+                                    + " contains multiple dots after a number.");
+                        }
+                        if (partIdx == 2 || !isNumber(nextC)) {
+                            extraInfoTmp = stringValue.substring(i);
+                            break;
+                        } else {
+                            partIdx++;
+                        }
+                    } else {
+                        extraInfoTmp = stringValue.substring(i);
+                        break;
+                    }
+                }
+            }
+            
+            if (extraInfoTmp != null) {
+                char firstChar = extraInfoTmp.charAt(0); 
+                if (firstChar == '.' || firstChar == '-' || firstChar == '_') {
+                    extraInfoTmp = extraInfoTmp.substring(1);
+                    if (extraInfoTmp.length() == 0) {
+                        throw new IllegalArgumentException(
+                            "The version number string " + _StringUtil.jQuote(stringValue)
+                            + " has an extra info section opened with \"" + firstChar + "\", but it's empty.");
+                    }
+                }
+            }
+        }
+        extraInfo = extraInfoTmp;
+        
+        major = parts[0];
+        minor = parts[1];
+        micro = parts[2];
+        intValue = calculateIntValue();
+        
+        this.gaeCompliant = gaeCompliant;
+        this.buildDate = buildDate;
+        
+    }
+
+    private boolean isNumber(char c) {
+        return c >= '0' && c <= '9';
+    }
+
+    public Version(int major, int minor, int micro) {
+        this(major, minor, micro, null, null, null);
+    }
+
+    /**
+     * Creates an object based on the {@code int} value that uses the same kind of encoding as {@link #intValue()}.
+     * 
+     * @since 2.3.24
+     */
+    public Version(int intValue) {
+        this.intValue = intValue;
+
+        micro = intValue % 1000;
+        minor = (intValue / 1000) % 1000;
+        major = intValue / 1000000;
+
+        extraInfo = null;
+        gaeCompliant = null;
+        buildDate = null;
+        originalStringValue = null;
+    }
+    
+    public Version(int major, int minor, int micro, String extraInfo, Boolean gaeCompliant, Date buildDate) {
+        this.major = major;
+        this.minor = minor;
+        this.micro = micro;
+        this.extraInfo = extraInfo;
+        this.gaeCompliant = gaeCompliant;
+        this.buildDate = buildDate;
+        intValue = calculateIntValue();
+        originalStringValue = null;
+    }
+
+    private int calculateIntValue() {
+        return intValueFor(major, minor, micro);
+    }
+    
+    static public int intValueFor(int major, int minor, int micro) {
+        return major * 1000000 + minor * 1000 + micro;
+    }
+    
+    private String getStringValue() {
+        if (originalStringValue != null) return originalStringValue;
+        
+        String calculatedStringValue = this.calculatedStringValue;
+        if (calculatedStringValue == null) {
+            synchronized (this) {
+                calculatedStringValue = this.calculatedStringValue;
+                if (calculatedStringValue == null) {
+                    calculatedStringValue = major + "." + minor + "." + micro;
+                    if (extraInfo != null) calculatedStringValue += "-" + extraInfo;
+                    this.calculatedStringValue = calculatedStringValue;
+                }
+            }
+        }
+        return calculatedStringValue;
+    }
+    
+    /**
+     * Contains the major.minor.micor numbers and the extraInfo part, not the other information.
+     */
+    @Override
+    public String toString() {
+        return getStringValue();
+    }
+
+    /**
+     * The 1st version number, like 1 in "1.2.3".
+     */
+    public int getMajor() {
+        return major;
+    }
+
+    /**
+     * The 2nd version number, like 2 in "1.2.3".
+     */
+    public int getMinor() {
+        return minor;
+    }
+
+    /**
+     * The 3rd version number, like 3 in "1.2.3".
+     */
+    public int getMicro() {
+        return micro;
+    }
+
+    /**
+     * The arbitrary string after the micro version number without leading dot, dash or underscore,
+     * like "RC03" in "2.4.0-RC03".
+     * This is usually a qualifier (RC, SNAPHOST, nightly, beta, etc) and sometimes build info (like
+     * date).
+     */
+    public String getExtraInfo() {
+        return extraInfo;
+    }
+    
+    /**
+     * @return The Google App Engine compliance, or {@code null}.
+     */
+    public Boolean isGAECompliant() {
+        return gaeCompliant;
+    }
+
+    /**
+     * @return The build date if known, or {@code null}.
+     */
+    public Date getBuildDate() {
+        return buildDate;
+    }
+
+    /**
+     * @return major * 1000000 + minor * 1000 + micro.
+     */
+    public int intValue() {
+        return intValue;
+    }
+
+    @Override
+    public int hashCode() {
+        int r = hashCode;
+        if (r != 0) return r;
+        synchronized (this) {
+            if (hashCode == 0) {
+                final int prime = 31;
+                int result = 1;
+                result = prime * result + (buildDate == null ? 0 : buildDate.hashCode());
+                result = prime * result + (extraInfo == null ? 0 : extraInfo.hashCode());
+                result = prime * result + (gaeCompliant == null ? 0 : gaeCompliant.hashCode());
+                result = prime * result + intValue;
+                if (result == 0) result = -1;  // 0 is reserved for "not set"
+                hashCode = result;
+            }
+            return hashCode;
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+
+        Version other = (Version) obj;
+
+        if (intValue != other.intValue) return false;
+        
+        if (other.hashCode() != hashCode()) return false;
+        
+        if (buildDate == null) {
+            if (other.buildDate != null) return false;
+        } else if (!buildDate.equals(other.buildDate)) {
+            return false;
+        }
+        
+        if (extraInfo == null) {
+            if (other.extraInfo != null) return false;
+        } else if (!extraInfo.equals(other.extraInfo)) {
+            return false;
+        }
+        
+        if (gaeCompliant == null) {
+            if (other.gaeCompliant != null) return false;
+        } else if (!gaeCompliant.equals(other.gaeCompliant)) {
+            return false;
+        }
+        
+        return true;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/WrongTemplateCharsetException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/WrongTemplateCharsetException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/WrongTemplateCharsetException.java
new file mode 100644
index 0000000..799efb4
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/WrongTemplateCharsetException.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 java.nio.charset.Charset;
+
+/**
+ * Thrown by the {@link Template} constructors that specify a non-{@code null} encoding whoch doesn't match the
+ * encoding specified in the {@code #ftl} header of the template.
+ */
+public class WrongTemplateCharsetException extends ParseException {
+    private static final long serialVersionUID = 1L;
+
+    private final Charset templateSpecifiedEncoding;
+    private final Charset constructorSpecifiedEncoding;
+
+    /**
+     * @since 2.3.22
+     */
+    public WrongTemplateCharsetException(Charset templateSpecifiedEncoding, Charset constructorSpecifiedEncoding) {
+        this.templateSpecifiedEncoding = templateSpecifiedEncoding;
+        this.constructorSpecifiedEncoding = constructorSpecifiedEncoding;
+    }
+
+    @Override
+    public String getMessage() {
+        return "Encoding specified inside the template (" + templateSpecifiedEncoding
+                + ") doesn't match the encoding specified for the Template constructor"
+                + (constructorSpecifiedEncoding != null ? " (" + constructorSpecifiedEncoding + ")." : ".");
+    }
+
+    /**
+     * @since 2.3.22
+     */
+    public Charset getTemplateSpecifiedEncoding() {
+        return templateSpecifiedEncoding;
+    }
+
+    /**
+     * @since 2.3.22
+     */
+    public Charset getConstructorSpecifiedEncoding() {
+        return constructorSpecifiedEncoding;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_CharsetBuilder.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CharsetBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CharsetBuilder.java
new file mode 100644
index 0000000..3234de8
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CharsetBuilder.java
@@ -0,0 +1,41 @@
+/*
+ * 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.nio.charset.Charset;
+
+import org.apache.freemarker.core.util._NullArgumentException;
+
+/**
+ * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ */
+public class _CharsetBuilder {
+
+    private final String name;
+
+    public _CharsetBuilder(String name) {
+        _NullArgumentException.check(name);
+        this.name = name;
+    }
+
+    public Charset build() {
+        return Charset.forName(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java
new file mode 100644
index 0000000..b575901
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java
@@ -0,0 +1,88 @@
+/*
+ * 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.TemplateModelException;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+/**
+ * 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 final class _CoreAPI {
+
+    public static final int VERSION_INT_3_0_0 = Configuration.VERSION_3_0_0.intValue();
+
+    // Can't be instantiated
+    private _CoreAPI() { }
+
+    /**
+     * ATTENTION: This is used by https://github.com/kenshoo/freemarker-online. Don't break backward
+     * compatibility without updating that project too! 
+     */
+    static public void addThreadInterruptedChecks(Template template) {
+        try {
+            new ThreadInterruptionSupportTemplatePostProcessor().postProcess(template);
+        } catch (TemplatePostProcessorException e) {
+            throw new RuntimeException("Template post-processing failed", e);
+        }
+    }
+
+    /**
+     * The work around the problematic cases where we should throw a {@link TemplateException}, but we are inside
+     * a {@link TemplateModel} method and so we can only throw {@link TemplateModelException}-s.  
+     */
+    // [FM3] Get rid of this problem, then delete this method
+    public static TemplateModelException ensureIsTemplateModelException(String modelOpMsg, TemplateException e) {
+        if (e instanceof TemplateModelException) {
+            return (TemplateModelException) e;
+        } else {
+            return new _TemplateModelException(
+                    e.getBlamedExpression(), e.getCause(), e.getEnvironment(), modelOpMsg);
+        }
+    }
+
+    // [FM3] Should become unnecessary as custom directive classes are reworked
+    public static boolean isMacroOrFunction(TemplateModel m) {
+        return m instanceof ASTDirMacro;
+    }
+
+    // [FM3] Should become unnecessary as custom directive classes are reworked
+    public static boolean isFunction(TemplateModel m) {
+        return m instanceof ASTDirMacro && ((ASTDirMacro) m).isFunction();
+    }
+
+    public static void checkVersionNotNullAndSupported(Version incompatibleImprovements) {
+        _NullArgumentException.check("incompatibleImprovements", incompatibleImprovements);
+        int iciV = incompatibleImprovements.intValue();
+        if (iciV > Configuration.getVersion().intValue()) {
+            throw new IllegalArgumentException("The FreeMarker version requested by \"incompatibleImprovements\" was "
+                    + incompatibleImprovements + ", but the installed FreeMarker version is only "
+                    + Configuration.getVersion() + ". You may need to upgrade FreeMarker in your project.");
+        }
+        if (iciV < VERSION_INT_3_0_0) {
+            throw new IllegalArgumentException("\"incompatibleImprovements\" must be at least 3.0.0, but was "
+                    + incompatibleImprovements);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreLogs.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreLogs.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreLogs.java
new file mode 100644
index 0000000..9179d7c
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreLogs.java
@@ -0,0 +1,46 @@
+/*
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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 final class _CoreLogs {
+
+    // [FM3] Why "Runtime"? "TemplateProcessing" maybe?
+    public static final Logger RUNTIME = LoggerFactory.getLogger("org.apache.freemarker.core.Runtime");
+    public static final Logger ATTEMPT = LoggerFactory.getLogger("org.apache.freemarker.core.Runtime.Attempt");
+    public static final Logger SECURITY = LoggerFactory.getLogger("org.apache.freemarker.core.Security");
+    public static final Logger OBJECT_WRAPPER = LoggerFactory.getLogger("org.apache.freemarker.core.model" +
+            ".ObjectWrapper");
+    public static final Logger TEMPLATE_RESOLVER = LoggerFactory.getLogger(
+            "org.apache.freemarker.core.templateresolver");
+    public static final Logger DEBUG_SERVER = LoggerFactory.getLogger("org.apache.freemarker.core.debug.server");
+    public static final Logger DEBUG_CLIENT = LoggerFactory.getLogger("org.apache.freemarker.core.debug.client");
+
+    private _CoreLogs() {
+        // Not meant to be instantiated
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.java
new file mode 100644
index 0000000..e15374b
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_Debug.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;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Created by √Čn on 2/26/2017.
+ */
+public final class _Debug {
+
+    private _Debug() {
+        //
+    }
+
+
+    public static void insertDebugBreak(Template t, int line) {
+        ASTElement te = findTemplateElement(t.getRootASTNode(), line);
+        if (te == null) {
+            return;
+        }
+        ASTElement parent = te.getParent();
+        ASTDebugBreak db = new ASTDebugBreak(te);
+        // TODO: Ensure there always is a parent by making sure
+        // that the root element in the template is always a ASTImplicitParent
+        // Also make sure it doesn't conflict with anyone's code.
+        parent.setChildAt(parent.getIndex(te), db);
+    }
+
+    public static void removeDebugBreak(Template t, int line) {
+        ASTElement te = findTemplateElement(t.getRootASTNode(), line);
+        if (te == null) {
+            return;
+        }
+        ASTDebugBreak db = null;
+        while (te != null) {
+            if (te instanceof ASTDebugBreak) {
+                db = (ASTDebugBreak) te;
+                break;
+            }
+            te = te.getParent();
+        }
+        if (db == null) {
+            return;
+        }
+        ASTElement parent = db.getParent();
+        parent.setChildAt(parent.getIndex(db), db.getChild(0));
+    }
+
+    private static ASTElement findTemplateElement(ASTElement te, int line) {
+        if (te.getBeginLine() > line || te.getEndLine() < line) {
+            return null;
+        }
+        // Find the narrowest match
+        List childMatches = new ArrayList();
+        for (Enumeration children = te.children(); children.hasMoreElements(); ) {
+            ASTElement child = (ASTElement) children.nextElement();
+            ASTElement childmatch = findTemplateElement(child, line);
+            if (childmatch != null) {
+                childMatches.add(childmatch);
+            }
+        }
+        //find a match that exactly matches the begin/end line
+        ASTElement bestMatch = null;
+        for (int i = 0; i < childMatches.size(); i++) {
+            ASTElement e = (ASTElement) childMatches.get(i);
+
+            if ( bestMatch == null ) {
+                bestMatch = e;
+            }
+
+            if ( e.getBeginLine() == line && e.getEndLine() > line ) {
+                bestMatch = e;
+            }
+
+            if ( e.getBeginLine() == e.getEndLine() && e.getBeginLine() == line) {
+                bestMatch = e;
+                break;
+            }
+        }
+        if ( bestMatch != null) {
+            return bestMatch;
+        }
+        // If no child provides narrower match, return this
+        return te;
+    }
+
+    public static void removeDebugBreaks(Template t) {
+        removeDebugBreaks(t.getRootASTNode());
+    }
+
+    private static void removeDebugBreaks(ASTElement te) {
+        int count = te.getChildCount();
+        for (int i = 0; i < count; ++i) {
+            ASTElement child = te.getChild(i);
+            while (child instanceof ASTDebugBreak) {
+                ASTElement dbchild = child.getChild(0);
+                te.setChildAt(i, dbchild);
+                child = dbchild;
+            }
+            removeDebugBreaks(child);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedAOrAn.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedAOrAn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedAOrAn.java
new file mode 100644
index 0000000..630fa26
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedAOrAn.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/** Don't use this; used internally by FreeMarker, might changes without notice. */
+public class _DelayedAOrAn extends _DelayedConversionToString {
+
+    public _DelayedAOrAn(Object object) {
+        super(object);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        String s = obj.toString();
+        return MessageUtil.getAOrAn(s) + " " + s;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedConversionToString.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedConversionToString.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedConversionToString.java
new file mode 100644
index 0000000..4fbe13f
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedConversionToString.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;
+
+/** Don't use this; used internally by FreeMarker, might changes without notice. */
+public abstract class _DelayedConversionToString {
+
+    private static final String NOT_SET = new String();
+
+    private Object object;
+    private volatile String stringValue = NOT_SET;
+
+    public _DelayedConversionToString(Object object) {
+        this.object = object;
+    }
+
+    @Override
+    public String toString() {
+        String stringValue = this.stringValue;
+        if (stringValue == NOT_SET) {
+            synchronized (this) {
+                stringValue = this.stringValue;
+                if (stringValue == NOT_SET) {
+                    stringValue = doConversion(object);
+                    this.stringValue = stringValue;
+                    object = null;
+                }
+            }
+        }
+        return stringValue;
+    }
+
+    protected abstract String doConversion(Object obj);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedFTLTypeDescription.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedFTLTypeDescription.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedFTLTypeDescription.java
new file mode 100644
index 0000000..21b6d55
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedFTLTypeDescription.java
@@ -0,0 +1,37 @@
+/*
+ * 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.FTLUtil;
+
+/** Don't use this; used internally by FreeMarker, might changes without notice. */
+public class _DelayedFTLTypeDescription extends _DelayedConversionToString {
+    
+    public _DelayedFTLTypeDescription(TemplateModel tm) {
+        super(tm);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        return FTLUtil.getTypeDescription((TemplateModel) obj);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetCanonicalForm.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetCanonicalForm.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetCanonicalForm.java
new file mode 100644
index 0000000..38a4cd8
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetCanonicalForm.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;
+
+
+/** Don't use this; used internally by FreeMarker, might changes without notice. */
+public class _DelayedGetCanonicalForm extends _DelayedConversionToString {
+    
+    public _DelayedGetCanonicalForm(ASTNode obj) {
+        super(obj);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        try {
+            return ((ASTNode) obj).getCanonicalForm();
+        } catch (Exception e) {
+            return "{Error getting canonical form}";
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetMessage.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetMessage.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetMessage.java
new file mode 100644
index 0000000..7bef399
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetMessage.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/** Don't use this; used internally by FreeMarker, might changes without notice. */
+public class _DelayedGetMessage extends _DelayedConversionToString {
+
+    public _DelayedGetMessage(Throwable exception) {
+        super(exception);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        final String message = ((Throwable) obj).getMessage();
+        return message == null || message.length() == 0 ? "[No exception message]" : message;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetMessageWithoutStackTop.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetMessageWithoutStackTop.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetMessageWithoutStackTop.java
new file mode 100644
index 0000000..7694c15
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedGetMessageWithoutStackTop.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/** Don't use this; used internally by FreeMarker, might changes without notice. */
+public class _DelayedGetMessageWithoutStackTop extends _DelayedConversionToString {
+
+    public _DelayedGetMessageWithoutStackTop(TemplateException exception) {
+        super(exception);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        return ((TemplateException) obj).getMessageWithoutStackTop();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedJQuote.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedJQuote.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedJQuote.java
new file mode 100644
index 0000000..4caf71b
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedJQuote.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 org.apache.freemarker.core.util._StringUtil;
+
+/** Don't use this; used internally by FreeMarker, might changes without notice. */
+public class _DelayedJQuote extends _DelayedConversionToString {
+
+    public _DelayedJQuote(Object object) {
+        super(object);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        return _StringUtil.jQuote(_ErrorDescriptionBuilder.toString(obj));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedJoinWithComma.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedJoinWithComma.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedJoinWithComma.java
new file mode 100644
index 0000000..7ae1da3
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedJoinWithComma.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+/** Don't use this; used internally by FreeMarker, might changes without notice. */
+public class _DelayedJoinWithComma extends _DelayedConversionToString {
+
+    public _DelayedJoinWithComma(String[] items) {
+        super(items);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        String[] items = (String[]) obj;
+        
+        int totalLength = 0;
+        for (int i = 0; i < items.length; i++) {
+            if (i != 0) totalLength += 2;
+            totalLength += items[i].length();
+        }
+        
+        StringBuilder sb = new StringBuilder(totalLength);
+        for (int i = 0; i < items.length; i++) {
+            if (i != 0) sb.append(", ");
+            sb.append(items[i]);
+        }
+        
+        return sb.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedOrdinal.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedOrdinal.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedOrdinal.java
new file mode 100644
index 0000000..443210d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedOrdinal.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/** 1 to "1st", 2 to "2nd", etc. */
+public class _DelayedOrdinal extends _DelayedConversionToString {
+
+    public _DelayedOrdinal(Object object) {
+        super(object);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        if (obj instanceof Number) {
+            long n = ((Number) obj).longValue();
+            if (n % 10 == 1 && n % 100 != 11) {
+                return n + "st";
+            } else if (n % 10 == 2 && n % 100 != 12) {
+                return n + "nd";
+            } else if (n % 10 == 3 && n % 100 != 13) {
+                return n + "rd";
+            } else {
+                return n + "th";
+            }
+        } else {
+            return "" + obj;
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedShortClassName.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedShortClassName.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedShortClassName.java
new file mode 100644
index 0000000..d9769b9
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedShortClassName.java
@@ -0,0 +1,35 @@
+/*
+ * 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.util._ClassUtil;
+
+public class _DelayedShortClassName extends _DelayedConversionToString {
+
+    public _DelayedShortClassName(Class pClass) {
+        super(pClass);
+    }
+
+    @Override
+    protected String doConversion(Object obj) {
+        return _ClassUtil.getShortClassName((Class) obj, true);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedToString.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedToString.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedToString.java
new file mode 100644
index 0000000..5eb5c54
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_DelayedToString.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+public class _DelayedToString extends _DelayedConversionToString {
+
+    public _DelayedToString(Object object) {
+        super(object);
+    }
+
+    public _DelayedToString(int object) {
+        super(Integer.valueOf(object));
+    }
+    
+    @Override
+    protected String doConversion(Object obj) {
+        return String.valueOf(obj);
+    }
+    
+}


Mime
View raw message