You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2022/12/18 16:54:46 UTC

[freemarker] 02/03: FREEMARKER-198: To avoid deadlock when class initialization happens on multiple threads (like _TemplateAPI->DefaultObjectWrapper, and DefaultObjectWrapper->_TemplateAPI), factored out static fields from _TemplateAPI into their owns classes.

This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git

commit 37b5b161a4ce5f2acdc3dac5c0beeade625048ba
Author: ddekany <dd...@apache.org>
AuthorDate: Sun Dec 18 17:19:00 2022 +0100

    FREEMARKER-198: To avoid deadlock when class initialization happens on multiple threads (like _TemplateAPI->DefaultObjectWrapper, and DefaultObjectWrapper->_TemplateAPI), factored out static fields from _TemplateAPI into their owns classes.
---
 src/main/java/freemarker/cache/TemplateCache.java  |   5 +-
 .../core/APINotSupportedTemplateException.java     |   4 +-
 .../java/freemarker/core/AddConcatExpression.java  |   6 +-
 .../java/freemarker/core/BuiltInsForDates.java     |   3 +-
 .../freemarker/core/BuiltInsForMultipleTypes.java  |   7 +-
 .../java/freemarker/core/BuiltInsForNodes.java     |   4 +-
 .../java/freemarker/core/BuiltInsForSequences.java |   6 +-
 .../freemarker/core/BuiltInsForStringsBasic.java   |   4 +-
 .../core/BuiltInsForStringsEncoding.java           |   4 +-
 .../freemarker/core/BuiltInsForStringsRegexp.java  |   4 +-
 src/main/java/freemarker/core/BuiltinVariable.java |   4 +-
 src/main/java/freemarker/core/Configurable.java    |   3 +-
 src/main/java/freemarker/core/DynamicKeyName.java  |  12 +-
 src/main/java/freemarker/core/Environment.java     |  25 +-
 src/main/java/freemarker/core/EvalUtil.java        |   4 +-
 .../freemarker/core/GetOptionalTemplateMethod.java |   4 +-
 src/main/java/freemarker/core/HashLiteral.java     |  12 +-
 src/main/java/freemarker/core/Interpret.java       |   4 +-
 src/main/java/freemarker/core/JSONParser.java      |  10 +-
 src/main/java/freemarker/core/ListLiteral.java     |   6 +-
 src/main/java/freemarker/core/Macro.java           |   6 +-
 src/main/java/freemarker/core/Range.java           |   3 +-
 src/main/java/freemarker/core/RecurseNode.java     |   4 +-
 .../freemarker/core/TemplateConfiguration.java     |   3 +-
 src/main/java/freemarker/core/VisitNode.java       |   4 +-
 src/main/java/freemarker/core/_CoreAPI.java        |   6 +-
 .../java/freemarker/ext/ant/FreemarkerXmlTask.java |   4 +-
 .../java/freemarker/ext/beans/BeansWrapper.java    |   9 +-
 .../ext/beans/BeansWrapperConfiguration.java       |   3 +-
 .../ext/beans/ClassIntrospectorBuilder.java        |   7 +-
 .../java/freemarker/ext/jdom/NodeListModel.java    |   4 +-
 .../freemarker/ext/jsp/FreeMarkerPageContext.java  |   6 +-
 .../freemarker/ext/servlet/InitParamParser.java    |   6 +-
 .../java/freemarker/template/Configuration.java    |   6 +-
 .../freemarker/template/DefaultObjectWrapper.java  |   6 +-
 .../DefaultObjectWrapperConfiguration.java         |   4 +-
 src/main/java/freemarker/template/Template.java    |   4 +-
 .../java/freemarker/template/_ObjectWrappers.java  |  58 ++++
 .../java/freemarker/template/_TemplateAPI.java     |  53 +---
 .../java/freemarker/template/_VersionInts.java     |  50 +++
 .../freemarker/template/utility/DOMNodeModel.java  |   4 +-
 .../template/utility/TemplateModelUtils.java       |   6 +-
 src/main/javacc/FTL.jj                             | 352 ++++++++++-----------
 .../freemarker/template/ConfigurationTest.java     |   2 +-
 .../template/DefaultObjectWrapperTest.java         |   2 +-
 .../test/templatesuite/TemplateTestCase.java       |   4 +-
 .../test/templatesuite/models/LegacyList.java      |   4 +-
 .../templatesuite/models/TransformHashWrapper.java |   4 +-
 48 files changed, 416 insertions(+), 339 deletions(-)

diff --git a/src/main/java/freemarker/cache/TemplateCache.java b/src/main/java/freemarker/cache/TemplateCache.java
index be19aeea..77c69578 100644
--- a/src/main/java/freemarker/cache/TemplateCache.java
+++ b/src/main/java/freemarker/cache/TemplateCache.java
@@ -40,6 +40,7 @@ import freemarker.template.MalformedTemplateNameException;
 import freemarker.template.Template;
 import freemarker.template.TemplateNotFoundException;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.NullArgumentException;
 import freemarker.template.utility.StringUtil;
 import freemarker.template.utility.UndeclaredThrowableException;
@@ -279,7 +280,7 @@ public class TemplateCache {
         } catch (MalformedTemplateNameException e) {
             // If we don't have to emulate backward compatible behavior, then just rethrow it: 
             if (templateNameFormat != TemplateNameFormat.DEFAULT_2_3_0
-                    || config.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_4_0) {
+                    || config.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_4_0) {
                 throw e;
             }
             return new MaybeMissingTemplate(null, e);
@@ -795,7 +796,7 @@ public class TemplateCache {
     private Object modifyForConfIcI(Object templateSource) {
         if (templateSource == null) return null;
         
-        if (config.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
+        if (config.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_21) {
             return templateSource;
         }
         
diff --git a/src/main/java/freemarker/core/APINotSupportedTemplateException.java b/src/main/java/freemarker/core/APINotSupportedTemplateException.java
index f21fa0e3..c706581c 100644
--- a/src/main/java/freemarker/core/APINotSupportedTemplateException.java
+++ b/src/main/java/freemarker/core/APINotSupportedTemplateException.java
@@ -25,7 +25,7 @@ import freemarker.template.SimpleHash;
 import freemarker.template.SimpleSequence;
 import freemarker.template.TemplateException;
 import freemarker.template.TemplateModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 
 /**
  * Thrown when {@code ?api} is not supported by a value.
@@ -56,7 +56,7 @@ class APINotSupportedTemplateException extends TemplateException {
                     desc.tip("In the FreeMarker configuration, \"", Configurable.OBJECT_WRAPPER_KEY,
                             "\" is a DefaultObjectWrapper with its \"useAdaptersForContainers\" property set to "
                             + "false. Setting it to true might solves this problem.");
-                    if (dow.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_22) {
+                    if (dow.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_22) {
                         desc.tip("Setting DefaultObjectWrapper's \"incompatibleImprovements\" to 2.3.22 or higher will "
                                 + "change the default value of \"useAdaptersForContainers\" to true.");
                     }
diff --git a/src/main/java/freemarker/core/AddConcatExpression.java b/src/main/java/freemarker/core/AddConcatExpression.java
index a4bfb9f1..f02be125 100644
--- a/src/main/java/freemarker/core/AddConcatExpression.java
+++ b/src/main/java/freemarker/core/AddConcatExpression.java
@@ -35,7 +35,7 @@ import freemarker.template.TemplateModelIterator;
 import freemarker.template.TemplateNumberModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
 /**
  * An operator for the + operator. Note that this is treated
@@ -274,7 +274,7 @@ final class AddConcatExpression extends Expression {
         throws TemplateModelException {
             if (keys == null) {
                 HashSet keySet = new HashSet();
-                SimpleSequence keySeq = new SimpleSequence(32, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+                SimpleSequence keySeq = new SimpleSequence(32, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 addKeys(keySet, keySeq, (TemplateHashModelEx) this.left);
                 addKeys(keySet, keySeq, (TemplateHashModelEx) this.right);
                 keys = new CollectionAndSequence(keySeq);
@@ -297,7 +297,7 @@ final class AddConcatExpression extends Expression {
         private void initValues()
         throws TemplateModelException {
             if (values == null) {
-                SimpleSequence seq = new SimpleSequence(size(), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+                SimpleSequence seq = new SimpleSequence(size(), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 // Note: size() invokes initKeys() if needed.
             
                 int ln = keys.size();
diff --git a/src/main/java/freemarker/core/BuiltInsForDates.java b/src/main/java/freemarker/core/BuiltInsForDates.java
index c4f660be..2a702660 100644
--- a/src/main/java/freemarker/core/BuiltInsForDates.java
+++ b/src/main/java/freemarker/core/BuiltInsForDates.java
@@ -33,6 +33,7 @@ import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.DateUtil;
 import freemarker.template.utility.UnrecognizedTimeZoneException;
 
@@ -206,7 +207,7 @@ class BuiltInsForDates {
             } else {
                 // java.sql.Time values meant to carry calendar field values only, so we don't show offset for them.
                 return !(date instanceof java.sql.Time
-                        && _TemplateAPI.getTemplateLanguageVersionAsInt(this) >= _TemplateAPI.VERSION_INT_2_3_21);
+                        && _TemplateAPI.getTemplateLanguageVersionAsInt(this) >= _VersionInts.V_2_3_21);
             }
         }
         
diff --git a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
index 87cce1fb..b214d8c3 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -48,6 +48,7 @@ import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
 import freemarker.template.TemplateTransformModel;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.NumberUtil;
 
 /**
@@ -114,7 +115,7 @@ class BuiltInsForMultipleTypes {
 
             @Override
             public int getMinimumICIVersion() {
-                return _TemplateAPI.VERSION_INT_2_3_21;
+                return _VersionInts.V_2_3_21;
             }
 
             @Override
@@ -136,7 +137,7 @@ class BuiltInsForMultipleTypes {
 
         @Override
         public int getMinimumICIVersion() {
-            return _TemplateAPI.VERSION_INT_2_3_32;
+            return _VersionInts.V_2_3_32;
         }
         
         @Override
@@ -364,7 +365,7 @@ class BuiltInsForMultipleTypes {
             TemplateModel tm = target.eval(env);
             target.assertNonNull(tm, env);
             return (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)
-                    && (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _TemplateAPI.VERSION_INT_2_3_21
+                    && (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _VersionInts.V_2_3_21
                         // These implement TemplateSequenceModel, yet they can't be #list-ed:
                         || !(tm instanceof SimpleMethodModel || tm instanceof OverloadedMethodsModel))
                     ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
diff --git a/src/main/java/freemarker/core/BuiltInsForNodes.java b/src/main/java/freemarker/core/BuiltInsForNodes.java
index 71e76bd9..ff75b6ee 100644
--- a/src/main/java/freemarker/core/BuiltInsForNodes.java
+++ b/src/main/java/freemarker/core/BuiltInsForNodes.java
@@ -30,7 +30,7 @@ import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateNodeModel;
 import freemarker.template.TemplateNodeModelEx;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
         /**
  * A holder for builtins that operate exclusively on (XML-)node left-hand value.
@@ -123,7 +123,7 @@ class BuiltInsForNodes {
         private Environment env;
         
         AncestorSequence(Environment env) {
-            super(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+            super(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
             this.env = env;
         }
         
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index b80f42e7..3d2b9fc8 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -45,7 +45,7 @@ import freemarker.template.TemplateModelListSequence;
 import freemarker.template.TemplateNumberModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 import freemarker.template.utility.Constants;
 import freemarker.template.utility.StringUtil;
 
@@ -889,8 +889,8 @@ class BuiltInsForSequences {
                         coll instanceof TemplateCollectionModelEx
                                 ? new SimpleSequence(
                                         ((TemplateCollectionModelEx) coll).size(),
-                                        _TemplateAPI.SAFE_OBJECT_WRAPPER)
-                                : new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+                                        _ObjectWrappers.SAFE_OBJECT_WRAPPER)
+                                : new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 for (TemplateModelIterator iter = coll.iterator(); iter.hasNext(); ) {
                     seq.add(iter.next());
                 }
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsBasic.java b/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
index 4ab8c40f..c97c93ed 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
@@ -35,7 +35,7 @@ import freemarker.template.TemplateMethodModelEx;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 import freemarker.template.utility.StringUtil;
 
 class BuiltInsForStringsBasic {
@@ -833,7 +833,7 @@ class BuiltInsForStringsBasic {
     static class word_listBI extends BuiltInForString {
         @Override
         TemplateModel calculateResult(String s, Environment env) {
-            SimpleSequence result = new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+            SimpleSequence result = new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
             StringTokenizer st = new StringTokenizer(s);
             while (st.hasMoreTokens()) {
                result.add(st.nextToken());
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
index f6001353..59dc3fe6 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
@@ -28,7 +28,7 @@ import freemarker.template.TemplateMethodModel;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.StringUtil;
 
 class BuiltInsForStringsEncoding {
@@ -51,7 +51,7 @@ class BuiltInsForStringsEncoding {
     
         @Override
         public int getMinimumICIVersion() {
-            return _TemplateAPI.VERSION_INT_2_3_20;
+            return _VersionInts.V_2_3_20;
         }
     
         @Override
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsRegexp.java b/src/main/java/freemarker/core/BuiltInsForStringsRegexp.java
index eddc97c1..d8be40bd 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsRegexp.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsRegexp.java
@@ -35,7 +35,7 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateModelIterator;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 import freemarker.template.utility.StringUtil;
 
 
@@ -145,7 +145,7 @@ class BuiltInsForStringsRegexp {
             MatchWithGroups(String input, Matcher matcher) {
                 matchedInputPart = input.substring(matcher.start(), matcher.end());
                 final int grpCount = matcher.groupCount() + 1;
-                groupsSeq = new SimpleSequence(grpCount, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+                groupsSeq = new SimpleSequence(grpCount, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 for (int i = 0; i < grpCount; i++) {
                     groupsSeq.add(matcher.group(i));
                 }
diff --git a/src/main/java/freemarker/core/BuiltinVariable.java b/src/main/java/freemarker/core/BuiltinVariable.java
index ccc9d0b6..b8f06a0a 100644
--- a/src/main/java/freemarker/core/BuiltinVariable.java
+++ b/src/main/java/freemarker/core/BuiltinVariable.java
@@ -32,7 +32,7 @@ import freemarker.template.TemplateHashModel;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.StringUtil;
 
 /**
@@ -222,7 +222,7 @@ final class BuiltinVariable extends Expression {
             // The behavior of env.getTemplate() was changed with IcI 2.3.22, but there was an unintended side effect
             // of changing the behavior of .template_name, which was fixed with IcI 2.3.23. IcI 2.3.22 deliberately
             // remains broken.
-            return (env.getConfiguration().getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_23)
+            return (env.getConfiguration().getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_23)
                     ? new SimpleScalar(env.getTemplate230().getName())
                     : new SimpleScalar(env.getTemplate().getName());
         }
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 676bd7f1..dd38163a 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -65,6 +65,7 @@ import freemarker.template.TemplateMethodModel;
 import freemarker.template.TemplateModel;
 import freemarker.template.Version;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.NullArgumentException;
 import freemarker.template.utility.StringUtil;
 
@@ -2096,7 +2097,7 @@ public class Configurable {
                     throw new IllegalArgumentException("List items must be String-s.");
                 }
                 addAutoInclude((String) templateName, this instanceof Configuration && ((Configuration) this)
-                        .getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_25);
+                        .getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_25);
             }
         }
     }
diff --git a/src/main/java/freemarker/core/DynamicKeyName.java b/src/main/java/freemarker/core/DynamicKeyName.java
index ec46af20..f8cef3a4 100644
--- a/src/main/java/freemarker/core/DynamicKeyName.java
+++ b/src/main/java/freemarker/core/DynamicKeyName.java
@@ -34,7 +34,9 @@ import freemarker.template.TemplateModelIterator;
 import freemarker.template.TemplateNumberModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
+import freemarker.template._ObjectWrappers;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.Constants;
 
 /**
@@ -291,7 +293,7 @@ final class DynamicKeyName extends Expression {
                 srcIdx += step;
             }
             // List items are already wrapped:
-            return new SimpleSequence(resultList, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+            return new SimpleSequence(resultList, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
         } else if (targetLazySeq != null) {
             // As a targetLazySeq can only occur if a new built-in like ?filter or ?map was used somewhere in the target
             // expression, in this case we can return lazily generated sequence without breaking backward compatibility.
@@ -386,7 +388,7 @@ final class DynamicKeyName extends Expression {
                 resultList.add(targetIter.next());
             }
             // List items are already wrapped:
-            return new SimpleSequence(resultList, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+            return new SimpleSequence(resultList, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
         }
     }
 
@@ -433,13 +435,13 @@ final class DynamicKeyName extends Expression {
                     "Range top index " + highIndex + " (0-based) is outside the sliced sequence of length " +
                     srcIdx + ".");
         }
-        return new SimpleSequence(Arrays.asList(resultElements), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+        return new SimpleSequence(Arrays.asList(resultElements), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
     }
 
     private TemplateModel emptyResult(boolean seq) {
         return seq
-                ? (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _TemplateAPI.VERSION_INT_2_3_21
-                        ? new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER)
+                ? (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _VersionInts.V_2_3_21
+                        ? new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER)
                         : Constants.EMPTY_SEQUENCE)
                 : TemplateScalarModel.EMPTY_STRING;
     }
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 617bb48c..a078eaea 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -73,7 +73,8 @@ import freemarker.template.TemplateSequenceModel;
 import freemarker.template.TemplateTransformModel;
 import freemarker.template.TransformControl;
 import freemarker.template.Version;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.DateUtil;
 import freemarker.template.utility.DateUtil.DateToISO8601CalendarFactory;
 import freemarker.template.utility.NullWriter;
@@ -218,7 +219,7 @@ public final class Environment extends Configurable {
     public Environment(Template template, final TemplateHashModel rootDataModel, Writer out) {
         super(template);
         configuration = template.getConfiguration();
-        incompatibleImprovementsGE2328 = configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_28;
+        incompatibleImprovementsGE2328 = configuration.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_28;
         this.globalNamespace = new Namespace(null);
         this.currentNamespace = mainNamespace = new Namespace(template);
         this.out = out;
@@ -520,7 +521,7 @@ public final class Environment extends Configurable {
                     if (tc != null
                             && !(t instanceof FlowControlException
                                     && getConfiguration().getIncompatibleImprovements().intValue()
-                                    >= _TemplateAPI.VERSION_INT_2_3_27)) {
+                                    >= _VersionInts.V_2_3_27)) {
                         tc.onError(t);
                     } else {
                         throw t;
@@ -739,7 +740,7 @@ public final class Environment extends Configurable {
     void invokeNodeHandlerFor(TemplateNodeModel node, TemplateSequenceModel namespaces)
             throws TemplateException, IOException {
         if (nodeNamespaces == null) {
-            SimpleSequence ss = new SimpleSequence(1, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+            SimpleSequence ss = new SimpleSequence(1, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
             ss.add(currentNamespace);
             nodeNamespaces = ss;
         }
@@ -1136,7 +1137,7 @@ public final class Environment extends Configurable {
 
     private static SimpleSequence initPositionalCatchAllParameter(Macro.Context macroCtx, String catchAllParamName) {
         SimpleSequence positionalCatchAllParamValue;
-        positionalCatchAllParamValue = new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+        positionalCatchAllParamValue = new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
         macroCtx.setLocalVar(catchAllParamName, positionalCatchAllParamValue);
         return positionalCatchAllParamValue;
     }
@@ -1144,7 +1145,7 @@ public final class Environment extends Configurable {
     private static SimpleHash initNamedCatchAllParameter(Macro.Context macroCtx, String catchAllParamName) {
         SimpleHash namedCatchAllParamValue;
         namedCatchAllParamValue = new SimpleHash(
-                new LinkedHashMap<String, Object>(), _TemplateAPI.SAFE_OBJECT_WRAPPER, 0);
+                new LinkedHashMap<String, Object>(), _ObjectWrappers.SAFE_OBJECT_WRAPPER, 0);
         macroCtx.setLocalVar(catchAllParamName, namedCatchAllParamValue);
         return namedCatchAllParamValue;
     }
@@ -1698,7 +1699,7 @@ public final class Environment extends Configurable {
      * @since 2.3.32
      */
     public TemplateNumberFormat getCTemplateNumberFormat() {
-        if (configuration.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_32) {
+        if (configuration.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_32) {
             ensureCNumberFormatInitialized();
             return cTemplateNumberFormat;
         }
@@ -1710,7 +1711,7 @@ public final class Environment extends Configurable {
     private void ensureCNumberFormatInitialized() {
         // Note: DecimalFormat-s aren't thread-safe, so you must clone the static field value.
         if (cNumberFormat == null) {
-            if (configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_31) {
+            if (configuration.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_31) {
                 cNumberFormat = (DecimalFormat) C_NUMBER_FORMAT_ICI_2_3_21.clone();
             } else {
                 cNumberFormat = (DecimalFormat) C_NUMBER_FORMAT_ICI_2_3_20.clone();
@@ -3308,12 +3309,12 @@ public final class Environment extends Configurable {
         private Template template;
 
         Namespace() {
-            super(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+            super(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
             this.template = Environment.this.getTemplate();
         }
 
         Namespace(Template template) {
-            super(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+            super(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
             this.template = template;
         }
 
@@ -3517,11 +3518,11 @@ public final class Environment extends Configurable {
     };
 
     private boolean isBeforeIcI2322() {
-        return configuration.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_22;
+        return configuration.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_22;
     }
 
     boolean isIcI2324OrLater() {
-        return configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_24;
+        return configuration.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_24;
     }
 
     /**
diff --git a/src/main/java/freemarker/core/EvalUtil.java b/src/main/java/freemarker/core/EvalUtil.java
index a3582084..3d3f900d 100644
--- a/src/main/java/freemarker/core/EvalUtil.java
+++ b/src/main/java/freemarker/core/EvalUtil.java
@@ -33,7 +33,7 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateNumberModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 
 /**
  * Internally used static utilities for evaluation expressions.
@@ -602,7 +602,7 @@ class EvalUtil {
         }
         if (env.getWrapUncheckedExceptions()) {
             return true;
-        } else if (env.getConfiguration().getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_27) {
+        } else if (env.getConfiguration().getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_27) {
             // We have to judge if we dare to wrap this exception, or it's too likely that some applications try to
             // catch it around the template processing to do something special. For the same reason, we only wrap very
             // frequent exceptions.
diff --git a/src/main/java/freemarker/core/GetOptionalTemplateMethod.java b/src/main/java/freemarker/core/GetOptionalTemplateMethod.java
index bb4d2f13..24a3581d 100644
--- a/src/main/java/freemarker/core/GetOptionalTemplateMethod.java
+++ b/src/main/java/freemarker/core/GetOptionalTemplateMethod.java
@@ -37,7 +37,7 @@ import freemarker.template.TemplateMethodModelEx;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 import freemarker.template.utility.TemplateModelUtils;
 
 /**
@@ -144,7 +144,7 @@ class GetOptionalTemplateMethod implements TemplateMethodModelEx {
                         "; see cause exception");
         }
         
-        SimpleHash result = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+        SimpleHash result = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
         result.put(RESULT_EXISTS, template != null);
         // If the template is missing, result.include and such will be missing too, so that a default can be
         // conveniently provided like in <@optTemp.include!myDefaultMacro />.
diff --git a/src/main/java/freemarker/core/HashLiteral.java b/src/main/java/freemarker/core/HashLiteral.java
index f358ba3c..cc6d2c97 100644
--- a/src/main/java/freemarker/core/HashLiteral.java
+++ b/src/main/java/freemarker/core/HashLiteral.java
@@ -31,7 +31,9 @@ import freemarker.template.TemplateHashModelEx2;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateModelIterator;
+import freemarker.template._ObjectWrappers;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 
 @SuppressWarnings("deprecation")
 final class HashLiteral extends Expression {
@@ -111,7 +113,7 @@ final class HashLiteral extends Expression {
         private TemplateCollectionModel keyCollection, valueCollection; // ordered lists of keys and values
 
         SequenceHash(Environment env) throws TemplateException {
-            if (_TemplateAPI.getTemplateLanguageVersionAsInt(HashLiteral.this) >= _TemplateAPI.VERSION_INT_2_3_21) {
+            if (_TemplateAPI.getTemplateLanguageVersionAsInt(HashLiteral.this) >= _VersionInts.V_2_3_21) {
                 map = new LinkedHashMap<>();
                 for (int i = 0; i < size; i++) {
                     Expression keyExp = keys.get(i);
@@ -127,8 +129,8 @@ final class HashLiteral extends Expression {
                 // Legacy hash literal, where repeated keys were kept when doing ?values or ?keys, yet overwritten when
                 // doing hash[key].
                 map = new HashMap<>();
-                SimpleSequence keyList = new SimpleSequence(size, _TemplateAPI.SAFE_OBJECT_WRAPPER);
-                SimpleSequence valueList = new SimpleSequence(size, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+                SimpleSequence keyList = new SimpleSequence(size, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
+                SimpleSequence valueList = new SimpleSequence(size, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 for (int i = 0; i < size; i++) {
                     Expression keyExp = keys.get(i);
                     Expression valExp = values.get(i);
@@ -156,7 +158,7 @@ final class HashLiteral extends Expression {
             if (keyCollection == null) {
                 // This can only happen when IcI >= 2.3.21, an the map is a LinkedHashMap.
                 keyCollection = new CollectionAndSequence(
-                        new SimpleSequence(map.keySet(), _TemplateAPI.SAFE_OBJECT_WRAPPER));
+                        new SimpleSequence(map.keySet(), _ObjectWrappers.SAFE_OBJECT_WRAPPER));
             }
             return keyCollection;
         }
@@ -166,7 +168,7 @@ final class HashLiteral extends Expression {
             if (valueCollection == null) {
                 // This can only happen when IcI >= 2.3.21, an the map is a LinkedHashMap.
                 valueCollection = new CollectionAndSequence(
-                        new SimpleSequence(map.values(), _TemplateAPI.SAFE_OBJECT_WRAPPER));
+                        new SimpleSequence(map.values(), _ObjectWrappers.SAFE_OBJECT_WRAPPER));
             }
             return valueCollection;
         }
diff --git a/src/main/java/freemarker/core/Interpret.java b/src/main/java/freemarker/core/Interpret.java
index 9b4cb418..4e6694ee 100644
--- a/src/main/java/freemarker/core/Interpret.java
+++ b/src/main/java/freemarker/core/Interpret.java
@@ -31,7 +31,7 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
 import freemarker.template.TemplateTransformModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 
 
 /**
@@ -81,7 +81,7 @@ class Interpret extends OutputFormatBoundBuiltIn {
         }
         String templateSource = sourceExpr.evalAndCoerceToPlainText(env);
         Template parentTemplate = env.getConfiguration().getIncompatibleImprovements().intValue()
-                >= _TemplateAPI.VERSION_INT_2_3_26 ? env.getCurrentTemplate() : env.getTemplate();
+                >= _VersionInts.V_2_3_26 ? env.getCurrentTemplate() : env.getTemplate();
         
         final Template interpretedTemplate;
         try {
diff --git a/src/main/java/freemarker/core/JSONParser.java b/src/main/java/freemarker/core/JSONParser.java
index ddb01c07..61fc4a9d 100644
--- a/src/main/java/freemarker/core/JSONParser.java
+++ b/src/main/java/freemarker/core/JSONParser.java
@@ -20,17 +20,13 @@
 package freemarker.core;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 
 import freemarker.template.SimpleHash;
 import freemarker.template.SimpleNumber;
 import freemarker.template.SimpleScalar;
 import freemarker.template.SimpleSequence;
-import freemarker.template.Template;
 import freemarker.template.TemplateBooleanModel;
 import freemarker.template.TemplateHashModelEx2;
 import freemarker.template.TemplateModel;
@@ -38,7 +34,7 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateNumberModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 import freemarker.template.utility.Constants;
 import freemarker.template.utility.NumberUtil;
 import freemarker.template.utility.StringUtil;
@@ -297,7 +293,7 @@ class JSONParser {
         if (tryConsumeChar(']')) return Constants.EMPTY_SEQUENCE;
 
         boolean afterComma = false;
-        SimpleSequence elements = new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+        SimpleSequence elements = new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
         do {
             skipWS();
             elements.add(consumeValue(afterComma ? null : UNCLOSED_ARRAY_MESSAGE, afterComma ? -1 : startP));
@@ -340,7 +336,7 @@ class JSONParser {
             skipWS();
             afterComma = true;
         } while (consumeChar(',', '}', UNCLOSED_OBJECT_MESSAGE, startP) == ',');
-        return new SimpleHash(map, _TemplateAPI.SAFE_OBJECT_WRAPPER, 0);
+        return new SimpleHash(map, _ObjectWrappers.SAFE_OBJECT_WRAPPER, 0);
     }
 
     private boolean isE(char c) {
diff --git a/src/main/java/freemarker/core/ListLiteral.java b/src/main/java/freemarker/core/ListLiteral.java
index 7745db72..d4f6de66 100644
--- a/src/main/java/freemarker/core/ListLiteral.java
+++ b/src/main/java/freemarker/core/ListLiteral.java
@@ -31,7 +31,7 @@ import freemarker.template.TemplateMethodModel;
 import freemarker.template.TemplateMethodModelEx;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
 final class ListLiteral extends Expression {
 
@@ -44,7 +44,7 @@ final class ListLiteral extends Expression {
 
     @Override
     TemplateModel _eval(Environment env) throws TemplateException {
-        SimpleSequence list = new SimpleSequence(items.size(), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+        SimpleSequence list = new SimpleSequence(items.size(), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
         for (Expression exp : items) {
             TemplateModel tm = exp.eval(env);
             if (env == null || !env.isClassicCompatible()) {
@@ -140,7 +140,7 @@ final class ListLiteral extends Expression {
     
     TemplateSequenceModel evaluateStringsToNamespaces(Environment env) throws TemplateException {
         TemplateSequenceModel val = (TemplateSequenceModel) eval(env);
-        SimpleSequence result = new SimpleSequence(val.size(), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+        SimpleSequence result = new SimpleSequence(val.size(), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
         for (int i = 0; i < items.size(); i++) {
             Object itemExpr = items.get(i);
             if (itemExpr instanceof StringLiteral) {
diff --git a/src/main/java/freemarker/core/Macro.java b/src/main/java/freemarker/core/Macro.java
index 31d621e7..878c4b2e 100644
--- a/src/main/java/freemarker/core/Macro.java
+++ b/src/main/java/freemarker/core/Macro.java
@@ -37,7 +37,7 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateModelIterator;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 import freemarker.template.utility.Constants;
 
 /**
@@ -344,7 +344,7 @@ public final class Macro extends TemplateElement implements TemplateModel {
                     }
 
                     SimpleSequence argsSpecVarValue = new SimpleSequence(
-                            lengthWithCatchAlls, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+                            lengthWithCatchAlls, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                     for (int paramIndex = 0; paramIndex < argsSpecVarDraft.length; paramIndex++) {
                         argsSpecVarValue.add(argsSpecVarDraft[paramIndex]);
                     }
@@ -379,7 +379,7 @@ public final class Macro extends TemplateElement implements TemplateModel {
 
                     SimpleHash argsSpecVarValue = new SimpleHash(
                             new LinkedHashMap<String, Object>(lengthWithCatchAlls * 4 / 3, 1.0f),
-                            _TemplateAPI.SAFE_OBJECT_WRAPPER, 0);
+                            _ObjectWrappers.SAFE_OBJECT_WRAPPER, 0);
                     for (int paramIndex = 0; paramIndex < argsSpecVarDraft.length; paramIndex++) {
                         argsSpecVarValue.put(paramNames[paramIndex], argsSpecVarDraft[paramIndex]);
                     }
diff --git a/src/main/java/freemarker/core/Range.java b/src/main/java/freemarker/core/Range.java
index a141c7c6..bd5620c2 100644
--- a/src/main/java/freemarker/core/Range.java
+++ b/src/main/java/freemarker/core/Range.java
@@ -22,6 +22,7 @@ package freemarker.core;
 import freemarker.template.TemplateException;
 import freemarker.template.TemplateModel;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 
 /**
  * A class that represents a Range between two integers.
@@ -56,7 +57,7 @@ final class Range extends Expression {
                     begin, endType != END_SIZE_LIMITED ? lhoValue : begin + lhoValue,
                     endType == END_INCLUSIVE, endType == END_SIZE_LIMITED); 
         } else {
-            return _TemplateAPI.getTemplateLanguageVersionAsInt(this) >= _TemplateAPI.VERSION_INT_2_3_21
+            return _TemplateAPI.getTemplateLanguageVersionAsInt(this) >= _VersionInts.V_2_3_21
                     ? new ListableRightUnboundedRangeModel(begin)
                     : new NonListableRightUnboundedRangeModel(begin);
         }
diff --git a/src/main/java/freemarker/core/RecurseNode.java b/src/main/java/freemarker/core/RecurseNode.java
index c7a4b095..e2d326e5 100644
--- a/src/main/java/freemarker/core/RecurseNode.java
+++ b/src/main/java/freemarker/core/RecurseNode.java
@@ -28,7 +28,7 @@ import freemarker.template.TemplateModel;
 import freemarker.template.TemplateNodeModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
 
 /**
@@ -58,7 +58,7 @@ final class RecurseNode extends TemplateElement {
         }
         if (nss != null) {
             if (nss instanceof TemplateHashModel) {
-                SimpleSequence ss = new SimpleSequence(1, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+                SimpleSequence ss = new SimpleSequence(1, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 ss.add(nss);
                 nss = ss;
             } else if (!(nss instanceof TemplateSequenceModel)) {
diff --git a/src/main/java/freemarker/core/TemplateConfiguration.java b/src/main/java/freemarker/core/TemplateConfiguration.java
index 92cc6718..ff290f5b 100644
--- a/src/main/java/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/freemarker/core/TemplateConfiguration.java
@@ -29,6 +29,7 @@ import freemarker.template.Configuration;
 import freemarker.template.Template;
 import freemarker.template.Version;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.NullArgumentException;
 
 /**
@@ -115,7 +116,7 @@ public final class TemplateConfiguration extends Configurable implements ParserC
             return;
         }
         
-        if (((Configuration) cfg).getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_22
+        if (((Configuration) cfg).getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_22
                 && hasAnyConfigurableSet()) {
             throw new IllegalStateException(
                     "This TemplateConfiguration can't be associated to a Configuration that has "
diff --git a/src/main/java/freemarker/core/VisitNode.java b/src/main/java/freemarker/core/VisitNode.java
index daaf0a7a..8d3006c5 100644
--- a/src/main/java/freemarker/core/VisitNode.java
+++ b/src/main/java/freemarker/core/VisitNode.java
@@ -27,7 +27,7 @@ import freemarker.template.TemplateModel;
 import freemarker.template.TemplateNodeModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
 
 /**
@@ -57,7 +57,7 @@ final class VisitNode extends TemplateElement {
         }
         if (nss != null) {
             if (nss instanceof Environment.Namespace) {
-                SimpleSequence ss = new SimpleSequence(1, _TemplateAPI.SAFE_OBJECT_WRAPPER);
+                SimpleSequence ss = new SimpleSequence(1, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 ss.add(nss);
                 nss = ss;
             } else if (!(nss instanceof TemplateSequenceModel)) {
diff --git a/src/main/java/freemarker/core/_CoreAPI.java b/src/main/java/freemarker/core/_CoreAPI.java
index 55a2d3d6..df522a13 100644
--- a/src/main/java/freemarker/core/_CoreAPI.java
+++ b/src/main/java/freemarker/core/_CoreAPI.java
@@ -40,9 +40,11 @@ import freemarker.template.utility.ClassUtil;
  * 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 _CoreAPI {
-    
+    // ATTENTION! Don't refer to other classes in the static initializer of this class! Fields that need that must be
+    // moved into separate class, to avoid class init deadlocks.
+
     public static final String ERROR_MESSAGE_HR = "----";
 
     // Can't be instantiated
diff --git a/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java b/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
index 58c5cb7e..46e99559 100644
--- a/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
+++ b/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
@@ -49,7 +49,7 @@ import freemarker.template.SimpleScalar;
 import freemarker.template.Template;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateNodeModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 import freemarker.template.utility.ClassUtil;
 import freemarker.template.utility.SecurityUtilities;
 
@@ -602,7 +602,7 @@ extends
     }
 
     private static TemplateModel wrapMap(Map table) {
-        SimpleHash model = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+        SimpleHash model = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
         for (Iterator it = table.entrySet().iterator(); it.hasNext(); ) {
             Map.Entry entry = (Map.Entry) it.next();
             model.put(String.valueOf(entry.getKey()), new SimpleScalar(String.valueOf(entry.getValue())));
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index c321558e..5ac4ea1f 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -67,6 +67,7 @@ import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
 import freemarker.template.Version;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.ClassUtil;
 import freemarker.template.utility.RichObjectWrapper;
 import freemarker.template.utility.WriteProtectable;
@@ -869,7 +870,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
     }
 
     static boolean is2321Bugfixed(Version version) {
-        return version.intValue() >= _TemplateAPI.VERSION_INT_2_3_21;
+        return version.intValue() >= _VersionInts.V_2_3_21;
     }
 
     boolean is2324Bugfixed() {
@@ -877,7 +878,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
     }
 
     static boolean is2324Bugfixed(Version version) {
-        return version.intValue() >= _TemplateAPI.VERSION_INT_2_3_24;
+        return version.intValue() >= _VersionInts.V_2_3_24;
     }
     
     /** 
@@ -886,8 +887,8 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
      */
     protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
         _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
-        return incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_27 ? Configuration.VERSION_2_3_27
-                : incompatibleImprovements.intValue() == _TemplateAPI.VERSION_INT_2_3_26 ? Configuration.VERSION_2_3_26
+        return incompatibleImprovements.intValue() >= _VersionInts.V_2_3_27 ? Configuration.VERSION_2_3_27
+                : incompatibleImprovements.intValue() == _VersionInts.V_2_3_26 ? Configuration.VERSION_2_3_26
                 : is2324Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_24
                 : is2321Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_21
                 : Configuration.VERSION_2_3_0;
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
index 7be7ee4b..ff43033d 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
@@ -24,6 +24,7 @@ import freemarker.template.ObjectWrapper;
 import freemarker.template.TemplateDateModel;
 import freemarker.template.Version;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 
 /**
  * Holds {@link BeansWrapper} configuration settings and defines their defaults.
@@ -89,7 +90,7 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
                 : BeansWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
         this.incompatibleImprovements = incompatibleImprovements;
         
-        preferIndexedReadMethod = incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_27;
+        preferIndexedReadMethod = incompatibleImprovements.intValue() < _VersionInts.V_2_3_27;
         
         classIntrospectorBuilder = new ClassIntrospectorBuilder(incompatibleImprovements);
     }
diff --git a/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java b/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
index cd4df0f5..76e42318 100644
--- a/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
+++ b/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
@@ -29,6 +29,7 @@ import java.util.Map;
 import freemarker.template.Configuration;
 import freemarker.template.Version;
 import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.NullArgumentException;
 
 final class ClassIntrospectorBuilder implements Cloneable {
@@ -69,15 +70,15 @@ final class ClassIntrospectorBuilder implements Cloneable {
         // to some version changes that affects BeansWrapper, but not the other way around.
         this.incompatibleImprovements = normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
         treatDefaultMethodsAsBeanMembers
-                = incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_26;
+                = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_26;
         memberAccessPolicy = DefaultMemberAccessPolicy.getInstance(this.incompatibleImprovements);
     }
 
     private static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
         _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
         // All breakpoints here must occur in BeansWrapper.normalizeIncompatibleImprovements!
-        return incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_30 ? Configuration.VERSION_2_3_30
-                : incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_21 ? Configuration.VERSION_2_3_21
+        return incompatibleImprovements.intValue() >= _VersionInts.V_2_3_30 ? Configuration.VERSION_2_3_30
+                : incompatibleImprovements.intValue() >= _VersionInts.V_2_3_21 ? Configuration.VERSION_2_3_21
                 : Configuration.VERSION_2_3_0;
     }
 
diff --git a/src/main/java/freemarker/ext/jdom/NodeListModel.java b/src/main/java/freemarker/ext/jdom/NodeListModel.java
index 7e963f74..dc669247 100644
--- a/src/main/java/freemarker/ext/jdom/NodeListModel.java
+++ b/src/main/java/freemarker/ext/jdom/NodeListModel.java
@@ -60,7 +60,7 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateModelIterator;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
 /**
  * Provides a template for wrapping JDOM objects. It is capable of storing not only
@@ -1168,7 +1168,7 @@ implements
     throws Exception {
         org.jdom.input.SAXBuilder builder = new org.jdom.input.SAXBuilder();
         Document document = builder.build(System.in);
-        SimpleHash model = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+        SimpleHash model = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
         model.put("document", new NodeListModel(document));
         FileReader fr = new FileReader(args[0]);
         Template template = new Template(args[0], fr);
diff --git a/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java b/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
index 724ba83f..041f8926 100644
--- a/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
+++ b/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
@@ -61,7 +61,7 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateModelIterator;
 import freemarker.template.TemplateNumberModel;
 import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.UndeclaredThrowableException;
 
 /**
@@ -195,7 +195,7 @@ abstract class FreeMarkerPageContext extends PageContext implements TemplateMode
             case PAGE_SCOPE: {
                 try {
                     final TemplateModel tm = environment.getGlobalNamespace().get(name);
-                    if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_22 && unwrapper != null) {
+                    if (incompatibleImprovements >= _VersionInts.V_2_3_22 && unwrapper != null) {
                         return unwrapper.unwrap(tm);
                     } else { // Legacy behavior branch
                         if (tm instanceof AdapterTemplateModel) {
@@ -213,7 +213,7 @@ abstract class FreeMarkerPageContext extends PageContext implements TemplateMode
                         if (tm instanceof TemplateBooleanModel) {
                             return Boolean.valueOf(((TemplateBooleanModel) tm).getAsBoolean());
                         }
-                        if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_22
+                        if (incompatibleImprovements >= _VersionInts.V_2_3_22
                                 && tm instanceof TemplateDateModel) {
                             return ((TemplateDateModel) tm).getAsDate();
                         }
diff --git a/src/main/java/freemarker/ext/servlet/InitParamParser.java b/src/main/java/freemarker/ext/servlet/InitParamParser.java
index 8791058c..0236e7e2 100644
--- a/src/main/java/freemarker/ext/servlet/InitParamParser.java
+++ b/src/main/java/freemarker/ext/servlet/InitParamParser.java
@@ -36,7 +36,7 @@ import freemarker.core._ObjectBuilderSettingEvaluator;
 import freemarker.core._SettingEvaluationEnvironment;
 import freemarker.log.Logger;
 import freemarker.template.Configuration;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.StringUtil;
 
 
@@ -82,7 +82,7 @@ final class InitParamParser {
             String filePath = pureTemplatePath.substring(TEMPLATE_PATH_PREFIX_FILE.length());
             templateLoader = new FileTemplateLoader(new File(filePath));
         } else if (pureTemplatePath.startsWith("[")
-                && cfg.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_22) {
+                && cfg.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_22) {
             if (!pureTemplatePath.endsWith("]")) {
                 // B.C. constraint: Can't throw any checked exceptions.
                 throw new TemplatePathParsingException("Failed to parse template path; closing \"]\" is missing.");
@@ -96,7 +96,7 @@ final class InitParamParser {
             }
             templateLoader = new MultiTemplateLoader(templateLoaders);
         } else if (pureTemplatePath.startsWith("{")
-                && cfg.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_22) {
+                && cfg.getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_22) {
             throw new TemplatePathParsingException("Template paths starting with \"{\" are reseved for future purposes");
         } else {
             templateLoader = new WebappTemplateLoader(srvCtx, pureTemplatePath);
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 11ac38bd..93bfd83d 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -1031,7 +1031,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     
     private static TemplateLoader createDefaultTemplateLoader(
             Version incompatibleImprovements, TemplateLoader existingTemplateLoader) {
-        if (incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
+        if (incompatibleImprovements.intValue() < _VersionInts.V_2_3_21) {
             if (existingTemplateLoader instanceof LegacyDefaultFileTemplateLoader) {
                 return existingTemplateLoader;
             }
@@ -2463,7 +2463,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     @Override
     public boolean getRecognizeStandardFileExtensions() {
         return recognizeStandardFileExtensions == null
-                ? incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_24
+                ? incompatibleImprovements.intValue() >= _VersionInts.V_2_3_24
                 : recognizeStandardFileExtensions.booleanValue();
     }
 
@@ -3667,7 +3667,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      * @since 2.3.21
      */
     public static ObjectWrapper getDefaultObjectWrapper(Version incompatibleImprovements) {
-        if (incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_21) {
+        if (incompatibleImprovements.intValue() < _VersionInts.V_2_3_21) {
             return ObjectWrapper.DEFAULT_WRAPPER;
         } else {
             return new DefaultObjectWrapperBuilder(incompatibleImprovements).build();
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapper.java b/src/main/java/freemarker/template/DefaultObjectWrapper.java
index eb44ce65..07b16229 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapper.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapper.java
@@ -133,7 +133,7 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
                 : new DefaultObjectWrapperConfiguration(bwCfg.getIncompatibleImprovements()) { }; 
         useAdaptersForContainers = dowDowCfg.getUseAdaptersForContainers();
         useAdapterForEnumerations = useAdaptersForContainers
-                && getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_26;
+                && getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_26;
         forceLegacyNonListCollections = dowDowCfg.getForceLegacyNonListCollections();
         iterableSupport = dowDowCfg.getIterableSupport();
         domNodeSupport = dowDowCfg.getDOMNodeSupport();
@@ -450,8 +450,8 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
     protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
         _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
         Version bwIcI = BeansWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
-        return incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_22
-                || bwIcI.intValue() >= _TemplateAPI.VERSION_INT_2_3_22
+        return incompatibleImprovements.intValue() < _VersionInts.V_2_3_22
+                || bwIcI.intValue() >= _VersionInts.V_2_3_22
                 ? bwIcI : Configuration.VERSION_2_3_22;
     }
 
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java b/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
index a9575bc1..da93feeb 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
@@ -43,8 +43,8 @@ public abstract class DefaultObjectWrapperConfiguration extends BeansWrapperConf
         _TemplateAPI.checkCurrentVersionNotRecycled(
                 incompatibleImprovements,
                 "freemarker.configuration", "DefaultObjectWrapper");
-        useAdaptersForContainers = getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_22;
-        forceLegacyNonListCollections = true; // [2.4]: = IcI < _TemplateAPI.VERSION_INT_2_4_0;
+        useAdaptersForContainers = getIncompatibleImprovements().intValue() >= _VersionInts.V_2_3_22;
+        forceLegacyNonListCollections = true; // [2.4]: = IcI < _TemplateAPI.V_2_4_0;
         domNodeSupport = true;
         jythonSupport = true;
     }
diff --git a/src/main/java/freemarker/template/Template.java b/src/main/java/freemarker/template/Template.java
index 578f48d9..06e9cb1a 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -343,9 +343,9 @@ public class Template extends Configurable {
     private static Version normalizeTemplateLanguageVersion(Version incompatibleImprovements) {
         _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
         int v = incompatibleImprovements.intValue();
-        if (v < _TemplateAPI.VERSION_INT_2_3_19) {
+        if (v < _VersionInts.V_2_3_19) {
             return Configuration.VERSION_2_3_0;
-        } else if (v > _TemplateAPI.VERSION_INT_2_3_21) {
+        } else if (v > _VersionInts.V_2_3_21) {
             return Configuration.VERSION_2_3_21;
         } else { // if 2.3.19 or 2.3.20 or 2.3.21
             return incompatibleImprovements;
diff --git a/src/main/java/freemarker/template/_ObjectWrappers.java b/src/main/java/freemarker/template/_ObjectWrappers.java
new file mode 100644
index 00000000..511531b9
--- /dev/null
+++ b/src/main/java/freemarker/template/_ObjectWrappers.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 freemarker.template;
+
+/**
+ * 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.
+ */
+// Be careful to not refer this class in the static initializers of other classes that (indirectly) refer to this class,
+// as that can lead to deadlock as the class initialization locks are acquired by the JVM! This is also why this was
+// extracted from _TemplateAPI.
+public final class _ObjectWrappers {
+    /**
+     * Kind of a dummy {@link ObjectWrapper} used at places where the internal code earlier used the
+     * {@link ObjectWrapper#DEFAULT_WRAPPER} singleton, because it wasn't supposed to wrap/unwrap anything with it;
+     * never use this {@link ObjectWrapper} in situations where values of arbitrary types need to be wrapped!
+     * The typical situation is that we are using {@link SimpleSequence}, or {@link SimpleHash}, which always has an
+     * {@link ObjectWrapper} field, even if we don't care in the given situation, and so we didn't set it explicitly.
+     * The concern with the old way is that the {@link ObjectWrapper} set in the {@link Configuration} is possibly
+     * more restrictive than the default, so if the template author can somehow make FreeMarker wrap something with the
+     * default {@link ObjectWrapper}, then we got a security problem. So we try not to have that around, if possible.
+     * The obvious fix, and the better engineering would be just use a {@link TemplateSequenceModel} or
+     * {@link TemplateHashModelEx2} implementation at those places, which doesn't have an {@link ObjectWrapper} (and
+     * doesn't have the overhead of said implementations either). But, some user code might casts the values it
+     * receives (as directive argument for example) to {@link SimpleSequence} or {@link SimpleHash}, instead of to
+     * {@link TemplateSequenceModel} or {@link TemplateHashModelEx2}. Such user code is wrong, but still, if it worked
+     * so far fine (especially as sequence/hash literals are implemented by these "Simple" classes), it's better if it
+     * keeps working when they upgrade to 2.3.30. Such user code will be still out of luck if it also tries to add items
+     * which are not handled by {@link SimpleObjectWrapper}, but such abuse is even more unlikely, and this is how far
+     * we could go with this backward compatibility hack.
+     *
+     * @since 2.3.30
+     */
+    public static final SimpleObjectWrapper SAFE_OBJECT_WRAPPER;
+    static {
+        SAFE_OBJECT_WRAPPER = new SimpleObjectWrapper(Configuration.VERSION_2_3_0);
+        SAFE_OBJECT_WRAPPER.writeProtect();
+    }
+
+}
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index a43842fe..51002b8b 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -36,55 +36,12 @@ import freemarker.template.utility.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. 
- */ 
+ * access things inside this package that users shouldn't.
+ */
 public class _TemplateAPI {
-    
-    // Constants for faster access... probably unnecessary and should be removed.
-    public static final int VERSION_INT_2_3_0 = Configuration.VERSION_2_3_0.intValue();
-    public static final int VERSION_INT_2_3_19 = Configuration.VERSION_2_3_19.intValue();
-    public static final int VERSION_INT_2_3_20 = Configuration.VERSION_2_3_20.intValue();
-    public static final int VERSION_INT_2_3_21 = Configuration.VERSION_2_3_21.intValue();
-    public static final int VERSION_INT_2_3_22 = Configuration.VERSION_2_3_22.intValue();
-    public static final int VERSION_INT_2_3_23 = Configuration.VERSION_2_3_23.intValue();
-    public static final int VERSION_INT_2_3_24 = Configuration.VERSION_2_3_24.intValue();
-    public static final int VERSION_INT_2_3_25 = Configuration.VERSION_2_3_25.intValue();
-    public static final int VERSION_INT_2_3_26 = Configuration.VERSION_2_3_26.intValue();
-    public static final int VERSION_INT_2_3_27 = Configuration.VERSION_2_3_27.intValue();
-    public static final int VERSION_INT_2_3_28 = Configuration.VERSION_2_3_28.intValue();
-    public static final int VERSION_INT_2_3_29 = Configuration.VERSION_2_3_29.intValue();
-    public static final int VERSION_INT_2_3_30 = Configuration.VERSION_2_3_30.intValue();
-    public static final int VERSION_INT_2_3_31 = Configuration.VERSION_2_3_31.intValue();
-    public static final int VERSION_INT_2_3_32 = Configuration.VERSION_2_3_32.intValue();
-    public static final int VERSION_INT_2_4_0 = Version.intValueFor(2, 4, 0);
+    // ATTENTION! Don't refer to other classes in the static initializer of this class! Fields that need that must be
+    // moved into separate class, as it was done in _VersionInts, and _ObjectWrappers, to avoid class init deadlocks.
 
-    /**
-     * Kind of a dummy {@link ObjectWrapper} used at places where the internal code earlier used the
-     * {@link ObjectWrapper#DEFAULT_WRAPPER} singleton, because it wasn't supposed to wrap/unwrap anything with it;
-     * never use this {@link ObjectWrapper} in situations where values of arbitrary types need to be wrapped!
-     * The typical situation is that we are using {@link SimpleSequence}, or {@link SimpleHash}, which always has an
-     * {@link ObjectWrapper} field, even if we don't care in the given situation, and so we didn't set it explicitly.
-     * The concern with the old way is that the {@link ObjectWrapper} set in the {@link Configuration} is possibly
-     * more restrictive than the default, so if the template author can somehow make FreeMarker wrap something with the
-     * default {@link ObjectWrapper}, then we got a security problem. So we try not to have that around, if possible.
-     * The obvious fix, and the better engineering would be just use a {@link TemplateSequenceModel} or
-     * {@link TemplateHashModelEx2} implementation at those places, which doesn't have an {@link ObjectWrapper} (and
-     * doesn't have the overhead of said implementations either). But, some user code might casts the values it
-     * receives (as directive argument for example) to {@link SimpleSequence} or {@link SimpleHash}, instead of to
-     * {@link TemplateSequenceModel} or {@link TemplateHashModelEx2}. Such user code is wrong, but still, if it worked
-     * so far fine (especially as sequence/hash literals are implemented by these "Simple" classes), it's better if it
-     * keeps working when they upgrade to 2.3.30. Such user code will be still out of luck if it also tries to add items
-     * which are not handled by {@link SimpleObjectWrapper}, but such abuse is even more unlikely, and this is how far
-     * we could go with this backward compatibility hack.
-     *
-     * @since 2.3.30
-     */
-    public static final SimpleObjectWrapper SAFE_OBJECT_WRAPPER;
-    static {
-        SAFE_OBJECT_WRAPPER = new SimpleObjectWrapper(Configuration.VERSION_2_3_0);
-        SAFE_OBJECT_WRAPPER.writeProtect();
-    }
-    
     public static void checkVersionNotNullAndSupported(Version incompatibleImprovements) {
         NullArgumentException.check("incompatibleImprovements", incompatibleImprovements);
         int iciV = incompatibleImprovements.intValue();
@@ -93,7 +50,7 @@ public class _TemplateAPI {
                     + incompatibleImprovements + ", but the installed FreeMarker version is only "
                     + Configuration.getVersion() + ". You may need to upgrade FreeMarker in your project.");
         }
-        if (iciV < VERSION_INT_2_3_0) {
+        if (iciV < _VersionInts.V_2_3_0) {
             throw new IllegalArgumentException("\"incompatibleImprovements\" must be at least 2.3.0.");
         }
     }
diff --git a/src/main/java/freemarker/template/_VersionInts.java b/src/main/java/freemarker/template/_VersionInts.java
new file mode 100644
index 00000000..22ba09d8
--- /dev/null
+++ b/src/main/java/freemarker/template/_VersionInts.java
@@ -0,0 +1,50 @@
+/*
+ * 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 freemarker.template;
+
+/**
+ * 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.
+ */
+// Because we refer to other classes in the static initializer of this class, be careful with referring this class in
+// the static initializers of other classes, as that can lead to deadlock if the class initialization locks are acquired
+// by the JVM in different orders! This is also why this was extracted from _TemplateAPI.
+public final class _VersionInts {
+    private _VersionInts() {
+    }
+
+    // Constants for faster access... probably unnecessary and should be removed.
+    public static final int V_2_3_0 = Configuration.VERSION_2_3_0.intValue();
+    public static final int V_2_3_19 = Configuration.VERSION_2_3_19.intValue();
+    public static final int V_2_3_20 = Configuration.VERSION_2_3_20.intValue();
+    public static final int V_2_3_21 = Configuration.VERSION_2_3_21.intValue();
+    public static final int V_2_3_22 = Configuration.VERSION_2_3_22.intValue();
+    public static final int V_2_3_23 = Configuration.VERSION_2_3_23.intValue();
+    public static final int V_2_3_24 = Configuration.VERSION_2_3_24.intValue();
+    public static final int V_2_3_25 = Configuration.VERSION_2_3_25.intValue();
+    public static final int V_2_3_26 = Configuration.VERSION_2_3_26.intValue();
+    public static final int V_2_3_27 = Configuration.VERSION_2_3_27.intValue();
+    public static final int V_2_3_28 = Configuration.VERSION_2_3_28.intValue();
+    public static final int V_2_3_30 = Configuration.VERSION_2_3_30.intValue();
+    public static final int V_2_3_31 = Configuration.VERSION_2_3_31.intValue();
+    public static final int V_2_3_32 = Configuration.VERSION_2_3_32.intValue();
+    public static final int V_2_4_0 = Version.intValueFor(2, 4, 0);
+}
diff --git a/src/main/java/freemarker/template/utility/DOMNodeModel.java b/src/main/java/freemarker/template/utility/DOMNodeModel.java
index b1c6f1b1..8a1ffef6 100644
--- a/src/main/java/freemarker/template/utility/DOMNodeModel.java
+++ b/src/main/java/freemarker/template/utility/DOMNodeModel.java
@@ -38,7 +38,7 @@ import freemarker.template.TemplateMethodModel;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateSequenceModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
 /**
  * A convenient wrapper class for wrapping a Node in the W3C DOM API.
@@ -74,7 +74,7 @@ public class DOMNodeModel implements TemplateHashModel {
             if ("attributes".equals(key)) {
                 NamedNodeMap attributes = node.getAttributes();
                 if (attributes != null) {
-                    SimpleHash hash = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+                    SimpleHash hash = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
                     for (int i = 0; i < attributes.getLength(); i++) {
                         Attr att = (Attr) attributes.item(i);
                         hash.put(att.getName(), att.getValue());
diff --git a/src/main/java/freemarker/template/utility/TemplateModelUtils.java b/src/main/java/freemarker/template/utility/TemplateModelUtils.java
index 4efff56c..a95f409c 100644
--- a/src/main/java/freemarker/template/utility/TemplateModelUtils.java
+++ b/src/main/java/freemarker/template/utility/TemplateModelUtils.java
@@ -39,7 +39,7 @@ import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateModelIterator;
 import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
 /**
  * Static utility method related to {@link TemplateModel}-s that didn't fit elsewhere.
@@ -258,7 +258,7 @@ public final class TemplateModelUtils {
         private void initKeys() throws TemplateModelException {
             if (keys == null) {
                 Set<String> keySet = new HashSet<>();
-                SimpleSequence keySeq = new SimpleSequence(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+                SimpleSequence keySeq = new SimpleSequence(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 for (TemplateHashModelEx hash : hashes) {
                     addKeys(keySet, keySeq, hash);
                 }
@@ -281,7 +281,7 @@ public final class TemplateModelUtils {
 
         private void initValues() throws TemplateModelException {
             if (values == null) {
-                SimpleSequence seq = new SimpleSequence(size(), _TemplateAPI.SAFE_OBJECT_WRAPPER);
+                SimpleSequence seq = new SimpleSequence(size(), _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                 // Note: size() invokes initKeys() if needed.
             
                 int ln = keys.size();
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index a6ee15b0..1ecfa4d8 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -292,22 +292,22 @@ public class FMParser {
         
         this.outputFormat = outputFormat;
         recalculateAutoEscapingField();                                
-        if (incompatibleImprovements < _TemplateAPI.VERSION_INT_2_3_24) {
+        if (incompatibleImprovements < _VersionInts.V_2_3_24) {
             // Emulate bug, where the string literal parser haven't inherited the IcI:
-            incompatibleImprovements = _TemplateAPI.VERSION_INT_2_3_0;
+            incompatibleImprovements = _VersionInts.V_2_3_0;
         }
-        
+
         // So that loop variable built-ins, like ?index, works inside the interpolations in the string literal:
         iteratorBlockContexts = parentParser.iteratorBlockContexts;
     }
 
     void tearDownStringLiteralMode(FMParser parentParser) {
         // If the naming convention was established inside the string literal, it's inherited by the parent:
-        FMParserTokenManager parentTokenSource = parentParser.token_source; 
+        FMParserTokenManager parentTokenSource = parentParser.token_source;
         parentTokenSource.namingConvention = token_source.namingConvention;
         parentTokenSource.namingConventionEstabilisher = token_source.namingConventionEstabilisher;
     }
-    
+
     /**
      * Used when we need to recreate the source code from the AST (such as for the FM2 to FM3 converter).
      */
@@ -350,7 +350,7 @@ public class FMParser {
         }
         return null;
     }
-    
+
     /**
      * Updates the {@link #autoEscaping} field based on the {@link #autoEscapingPolicy} and {@link #outputFormat} fields.
      */
@@ -369,7 +369,7 @@ public class FMParser {
             autoEscaping = false;
         }
     }
-    
+
     MarkupOutputFormat getMarkupOutputFormat() {
         return outputFormat instanceof MarkupOutputFormat ? (MarkupOutputFormat) outputFormat : null;
     }
@@ -382,7 +382,7 @@ public class FMParser {
                 ? Configuration.SQUARE_BRACKET_TAG_SYNTAX
                 : Configuration.ANGLE_BRACKET_TAG_SYNTAX;
     }
-    
+
     /**
      * Don't use it, unless you are developing FreeMarker itself.
      * The naming convention used by this template; if it couldn't be detected so far, it will be the most probable one.
@@ -513,14 +513,14 @@ public class FMParser {
         }
         throw new ParseException("Expecting boolean (true/false) parameter", exp);
     }
-    
+
     void checkCurrentOutputFormatCanEscape(Token start) throws ParseException {
         if (!(outputFormat instanceof MarkupOutputFormat)) {
             throw new ParseException("The current output format can't do escaping: " + outputFormat,
                     template, start);
         }
-    }    
-    
+    }
+
     private ParserIteratorBlockContext pushIteratorBlockContext() {
         if (iteratorBlockContexts == null) {
             iteratorBlockContexts = new ArrayList<ParserIteratorBlockContext>(4);
@@ -529,16 +529,16 @@ public class FMParser {
         iteratorBlockContexts.add(newCtx);
         return newCtx;
     }
-    
+
     private void popIteratorBlockContext() {
         iteratorBlockContexts.remove(iteratorBlockContexts.size() - 1);
     }
-    
+
     private ParserIteratorBlockContext peekIteratorBlockContext() {
         int size = iteratorBlockContexts != null ? iteratorBlockContexts.size() : 0;
-        return size != 0 ? (ParserIteratorBlockContext) iteratorBlockContexts.get(size - 1) : null; 
+        return size != 0 ? (ParserIteratorBlockContext) iteratorBlockContexts.get(size - 1) : null;
     }
-    
+
     private void checkLoopVariableBuiltInLHO(String loopVarName, Expression lhoExp, Token biName)
             throws ParseException {
         int size = iteratorBlockContexts != null ? iteratorBlockContexts.size() : 0;
@@ -560,8 +560,8 @@ public class FMParser {
                 + "but there's no loop variable in scope with this name: " + loopVarName,
                 lhoExp);
     }
-    
-	private String forEachDirectiveSymbol() {    
+
+	private String forEachDirectiveSymbol() {
 	    // [2.4] Use camel case as the default
 	    return token_source.namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION ? "#forEach" : "#foreach";
 	}
@@ -685,7 +685,7 @@ TOKEN_MGR_DECLS:
         // We only get here if this is a strict FTL tag.
         tagSyntaxEstablished = true;
         
-        if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+        if (incompatibleImprovements >= _VersionInts.V_2_3_28
                 || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX) {
 	        // For END_xxx tags, as they can't contain expressions, the whole tag is a single token. So this is the only
 	        // chance to check if we got something inconsistent like `</#if]`. (We can't do this at the #CLOSE_TAG1 or
@@ -702,16 +702,16 @@ TOKEN_MGR_DECLS:
                 }
             } // if end-tag
         }
-        
+
         checkNamingConvention(tok, tokenNamingConvention);
-        
+
         SwitchTo(newLexState);
     }
 
     void checkNamingConvention(Token tok) {
-        checkNamingConvention(tok, _CoreStringUtils.getIdentifierNamingConvention(tok.image)); 
+        checkNamingConvention(tok, _CoreStringUtils.getIdentifierNamingConvention(tok.image));
     }
-    
+
     void checkNamingConvention(Token tok, int tokenNamingConvention) {
         if (tokenNamingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION) {
 	        if (namingConvention == Configuration.AUTO_DETECT_NAMING_CONVENTION) {
@@ -722,7 +722,7 @@ TOKEN_MGR_DECLS:
 	        }
         }
     }
-    
+
     private TokenMgrError newNameConventionMismatchException(Token tok) {
         return new TokenMgrError(
                 "Naming convention mismatch. "
@@ -733,7 +733,7 @@ TOKEN_MGR_DECLS:
                 + (namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION
                             ? "camel case naming convention (like: exampleName) "
                             : (namingConvention == Configuration.LEGACY_NAMING_CONVENTION
-                                    ? "legacy naming convention (directive (tag) names are like examplename, " 
+                                    ? "legacy naming convention (directive (tag) names are like examplename, "
                                       + "everything else is like example_name) "
                                     : "??? (internal error)"
                                     ))
@@ -755,11 +755,11 @@ TOKEN_MGR_DECLS:
     private void handleTagSyntaxAndSwitch(Token tok, int newLexState) {
         handleTagSyntaxAndSwitch(tok, Configuration.AUTO_DETECT_NAMING_CONVENTION, newLexState);
     }
-    
+
     private boolean isStrictTag(String image) {
         return image.length() > 2 && (image.charAt(1) == '#' || image.charAt(2) == '#');
     }
-    
+
     /**
      * Detects the naming convention used, both in start- and end-tag tokens.
      *
@@ -773,7 +773,7 @@ TOKEN_MGR_DECLS:
 
     static char getTagNameCharAt(Token tok, int charIdxInName) {
         final String image = tok.image;
-        
+
         // Skip tag delimiter:
         int idx = 0;
         for (;;) {
@@ -817,7 +817,7 @@ TOKEN_MGR_DECLS:
     }
 
     private void startInterpolation(Token tok) {
-        if ( 
+        if (
                 interpolationSyntax == LEGACY_INTERPOLATION_SYNTAX
                     && tok.kind == SQUARE_BRACKET_INTERPOLATION_OPENING
                 || interpolationSyntax == DOLLAR_INTERPOLATION_SYNTAX
@@ -827,7 +827,7 @@ TOKEN_MGR_DECLS:
             tok.kind = STATIC_TEXT_NON_WS;
             return;
         }
-        
+
         if (postInterpolationLexState != -1) {
             // This certainly never occurs, as starting an interpolation in expression mode fails earlier.
             char c = tok.image.charAt(0);
@@ -847,7 +847,7 @@ TOKEN_MGR_DECLS:
         SwitchTo(postInterpolationLexState);
         postInterpolationLexState = -1;
     }
-    
+
     private TokenMgrError newUnexpectedClosingTokenException(Token closingTk) {
             return new TokenMgrError(
                     "You can't have an \"" + closingTk.image + "\" here, as there's nothing open that it could close.",
@@ -921,7 +921,7 @@ TOKEN:
      */
     <ATTEMPT : <START_TAG> "attempt" <CLOSE_TAG1>> { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
     |
-    <RECOVER : <START_TAG> "recover" <CLOSE_TAG1>> { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); } 
+    <RECOVER : <START_TAG> "recover" <CLOSE_TAG1>> { handleTagSyntaxAndSwitch(matchedToken, DEFAULT); }
     |
     <IF : <START_TAG> "if" <BLANK>> { handleTagSyntaxAndSwitch(matchedToken, FM_EXPRESSION); }
     |
@@ -1096,7 +1096,7 @@ TOKEN:
      */
     <UNKNOWN_DIRECTIVE : ("[#" | "[/#" | "<#" | "</#") (["a"-"z", "A"-"Z", "_"])+>
     {
-        if (!tagSyntaxEstablished && incompatibleImprovements < _TemplateAPI.VERSION_INT_2_3_19) {
+        if (!tagSyntaxEstablished && incompatibleImprovements < _VersionInts.V_2_3_19) {
             matchedToken.kind = STATIC_TEXT_NON_WS;
         } else {
             char firstChar = matchedToken.image.charAt(0);
@@ -1121,7 +1121,7 @@ TOKEN:
                 // unknown directive:
                 if (_CoreAPI.ALL_BUILT_IN_DIRECTIVE_NAMES.contains(dn)) {
                     throw new TokenMgrError(
-                            "#" + dn + " is an existing directive, but the tag is malformed. " 
+                            "#" + dn + " is an existing directive, but the tag is malformed. "
                             + " (See FreeMarker Manual / Directive Reference.)",
                             TokenMgrError.LEXICAL_ERROR,
                             matchedToken.beginLine, matchedToken.beginColumn + 1,
@@ -1214,7 +1214,7 @@ TOKEN:
             ("x" ["0"-"9", "A"-"F", "a"-"f"])
         )
     >
-    | 
+    |
     <STRING_LITERAL :
         (
             "\""
@@ -1322,12 +1322,12 @@ TOKEN:
             // There's a legacy glitch where you can always close a tag with `]`, like `<#if x]`. We have to keep that
             // working for backward compatibility, hence we don't always throw at !squBracTagSyntax:
             if (!squBracTagSyntax
-                    && (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+                    && (incompatibleImprovements >= _VersionInts.V_2_3_28
                             || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)
                     || postInterpolationLexState != -1 /* We're in an interpolation => We aren't in a tag */) {
                 throw newUnexpectedClosingTokenException(matchedToken);
             }
-            
+
             // Close tag, either legally or to emulate legacy glitch:
             matchedToken.kind = DIRECTIVE_END;
             if (inFTLHeader) {
@@ -1379,7 +1379,7 @@ TOKEN:
         // Remove backslashes from Token.image:
         final String s = matchedToken.image;
         if (s.indexOf('\\') != -1) {
-            final int srcLn = s.length(); 
+            final int srcLn = s.length();
             final char[] newS = new char[srcLn - 1];
             int dstIdx = 0;
             for (int srcIdx = 0; srcIdx < srcLn; srcIdx++) {
@@ -1411,136 +1411,136 @@ TOKEN:
     <#NON_ESCAPED_ID_START_CHAR:
         [
             // This was generated on JDK 1.8.0_20 Win64 with src/main/misc/identifierChars/IdentifierCharGenerator.java
-			"$", 
-			"@" - "Z", 
-			"_", 
-			"a" - "z", 
-			"\u00AA", 
-			"\u00B5", 
-			"\u00BA", 
-			"\u00C0" - "\u00D6", 
-			"\u00D8" - "\u00F6", 
-			"\u00F8" - "\u1FFF", 
-			"\u2071", 
-			"\u207F", 
-			"\u2090" - "\u209C", 
-			"\u2102", 
-			"\u2107", 
-			"\u210A" - "\u2113", 
-			"\u2115", 
-			"\u2119" - "\u211D", 
-			"\u2124", 
-			"\u2126", 
-			"\u2128", 
-			"\u212A" - "\u212D", 
-			"\u212F" - "\u2139", 
-			"\u213C" - "\u213F", 
-			"\u2145" - "\u2149", 
-			"\u214E", 
-			"\u2183" - "\u2184", 
-			"\u2C00" - "\u2C2E", 
-			"\u2C30" - "\u2C5E", 
-			"\u2C60" - "\u2CE4", 
-			"\u2CEB" - "\u2CEE", 
-			"\u2CF2" - "\u2CF3", 
-			"\u2D00" - "\u2D25", 
-			"\u2D27", 
-			"\u2D2D", 
-			"\u2D30" - "\u2D67", 
-			"\u2D6F", 
-			"\u2D80" - "\u2D96", 
-			"\u2DA0" - "\u2DA6", 
-			"\u2DA8" - "\u2DAE", 
-			"\u2DB0" - "\u2DB6", 
-			"\u2DB8" - "\u2DBE", 
-			"\u2DC0" - "\u2DC6", 
-			"\u2DC8" - "\u2DCE", 
-			"\u2DD0" - "\u2DD6", 
-			"\u2DD8" - "\u2DDE", 
-			"\u2E2F", 
-			"\u3005" - "\u3006", 
-			"\u3031" - "\u3035", 
-			"\u303B" - "\u303C", 
-			"\u3040" - "\u318F", 
-			"\u31A0" - "\u31BA", 
-			"\u31F0" - "\u31FF", 
-			"\u3300" - "\u337F", 
-			"\u3400" - "\u4DB5", 
-			"\u4E00" - "\uA48C", 
-			"\uA4D0" - "\uA4FD", 
-			"\uA500" - "\uA60C", 
-			"\uA610" - "\uA62B", 
-			"\uA640" - "\uA66E", 
-			"\uA67F" - "\uA697", 
-			"\uA6A0" - "\uA6E5", 
-			"\uA717" - "\uA71F", 
-			"\uA722" - "\uA788", 
-			"\uA78B" - "\uA78E", 
-			"\uA790" - "\uA793", 
-			"\uA7A0" - "\uA7AA", 
-			"\uA7F8" - "\uA801", 
-			"\uA803" - "\uA805", 
-			"\uA807" - "\uA80A", 
-			"\uA80C" - "\uA822", 
-			"\uA840" - "\uA873", 
-			"\uA882" - "\uA8B3", 
-			"\uA8D0" - "\uA8D9", 
-			"\uA8F2" - "\uA8F7", 
-			"\uA8FB", 
-			"\uA900" - "\uA925", 
-			"\uA930" - "\uA946", 
-			"\uA960" - "\uA97C", 
-			"\uA984" - "\uA9B2", 
-			"\uA9CF" - "\uA9D9", 
-			"\uAA00" - "\uAA28", 
-			"\uAA40" - "\uAA42", 
-			"\uAA44" - "\uAA4B", 
-			"\uAA50" - "\uAA59", 
-			"\uAA60" - "\uAA76", 
-			"\uAA7A", 
-			"\uAA80" - "\uAAAF", 
-			"\uAAB1", 
-			"\uAAB5" - "\uAAB6", 
-			"\uAAB9" - "\uAABD", 
-			"\uAAC0", 
-			"\uAAC2", 
-			"\uAADB" - "\uAADD", 
-			"\uAAE0" - "\uAAEA", 
-			"\uAAF2" - "\uAAF4", 
-			"\uAB01" - "\uAB06", 
-			"\uAB09" - "\uAB0E", 
-			"\uAB11" - "\uAB16", 
-			"\uAB20" - "\uAB26", 
-			"\uAB28" - "\uAB2E", 
-			"\uABC0" - "\uABE2", 
-			"\uABF0" - "\uABF9", 
-			"\uAC00" - "\uD7A3", 
-			"\uD7B0" - "\uD7C6", 
-			"\uD7CB" - "\uD7FB", 
-			"\uF900" - "\uFB06", 
-			"\uFB13" - "\uFB17", 
-			"\uFB1D", 
-			"\uFB1F" - "\uFB28", 
-			"\uFB2A" - "\uFB36", 
-			"\uFB38" - "\uFB3C", 
-			"\uFB3E", 
-			"\uFB40" - "\uFB41", 
-			"\uFB43" - "\uFB44", 
-			"\uFB46" - "\uFBB1", 
-			"\uFBD3" - "\uFD3D", 
-			"\uFD50" - "\uFD8F", 
-			"\uFD92" - "\uFDC7", 
-			"\uFDF0" - "\uFDFB", 
-			"\uFE70" - "\uFE74", 
-			"\uFE76" - "\uFEFC", 
-			"\uFF10" - "\uFF19", 
-			"\uFF21" - "\uFF3A", 
-			"\uFF41" - "\uFF5A", 
-			"\uFF66" - "\uFFBE", 
-			"\uFFC2" - "\uFFC7", 
-			"\uFFCA" - "\uFFCF", 
-			"\uFFD2" - "\uFFD7", 
-			"\uFFDA" - "\uFFDC" 
+			"$",
+			"@" - "Z",
+			"_",
+			"a" - "z",
+			"\u00AA",
+			"\u00B5",
+			"\u00BA",
+			"\u00C0" - "\u00D6",
+			"\u00D8" - "\u00F6",
+			"\u00F8" - "\u1FFF",
+			"\u2071",
+			"\u207F",
+			"\u2090" - "\u209C",
+			"\u2102",
+			"\u2107",
+			"\u210A" - "\u2113",
+			"\u2115",
+			"\u2119" - "\u211D",
+			"\u2124",
+			"\u2126",
+			"\u2128",
+			"\u212A" - "\u212D",
+			"\u212F" - "\u2139",
+			"\u213C" - "\u213F",
+			"\u2145" - "\u2149",
+			"\u214E",
+			"\u2183" - "\u2184",
+			"\u2C00" - "\u2C2E",
+			"\u2C30" - "\u2C5E",
+			"\u2C60" - "\u2CE4",
+			"\u2CEB" - "\u2CEE",
+			"\u2CF2" - "\u2CF3",
+			"\u2D00" - "\u2D25",
+			"\u2D27",
+			"\u2D2D",
+			"\u2D30" - "\u2D67",
+			"\u2D6F",
+			"\u2D80" - "\u2D96",
+			"\u2DA0" - "\u2DA6",
+			"\u2DA8" - "\u2DAE",
+			"\u2DB0" - "\u2DB6",
+			"\u2DB8" - "\u2DBE",
+			"\u2DC0" - "\u2DC6",
+			"\u2DC8" - "\u2DCE",
+			"\u2DD0" - "\u2DD6",
+			"\u2DD8" - "\u2DDE",
+			"\u2E2F",
+			"\u3005" - "\u3006",
+			"\u3031" - "\u3035",
+			"\u303B" - "\u303C",
+			"\u3040" - "\u318F",
+			"\u31A0" - "\u31BA",
+			"\u31F0" - "\u31FF",
+			"\u3300" - "\u337F",
+			"\u3400" - "\u4DB5",
+			"\u4E00" - "\uA48C",
+			"\uA4D0" - "\uA4FD",
+			"\uA500" - "\uA60C",
+			"\uA610" - "\uA62B",
+			"\uA640" - "\uA66E",
+			"\uA67F" - "\uA697",
+			"\uA6A0" - "\uA6E5",
+			"\uA717" - "\uA71F",
+			"\uA722" - "\uA788",
+			"\uA78B" - "\uA78E",
+			"\uA790" - "\uA793",
+			"\uA7A0" - "\uA7AA",
+			"\uA7F8" - "\uA801",
+			"\uA803" - "\uA805",
+			"\uA807" - "\uA80A",
+			"\uA80C" - "\uA822",
+			"\uA840" - "\uA873",
+			"\uA882" - "\uA8B3",
+			"\uA8D0" - "\uA8D9",
+			"\uA8F2" - "\uA8F7",
+			"\uA8FB",
+			"\uA900" - "\uA925",
+			"\uA930" - "\uA946",
+			"\uA960" - "\uA97C",
+			"\uA984" - "\uA9B2",
+			"\uA9CF" - "\uA9D9",
+			"\uAA00" - "\uAA28",
+			"\uAA40" - "\uAA42",
+			"\uAA44" - "\uAA4B",
+			"\uAA50" - "\uAA59",
+			"\uAA60" - "\uAA76",
+			"\uAA7A",
+			"\uAA80" - "\uAAAF",
+			"\uAAB1",
+			"\uAAB5" - "\uAAB6",
+			"\uAAB9" - "\uAABD",
+			"\uAAC0",
+			"\uAAC2",
+			"\uAADB" - "\uAADD",
+			"\uAAE0" - "\uAAEA",
+			"\uAAF2" - "\uAAF4",
+			"\uAB01" - "\uAB06",
+			"\uAB09" - "\uAB0E",
+			"\uAB11" - "\uAB16",
+			"\uAB20" - "\uAB26",
+			"\uAB28" - "\uAB2E",
+			"\uABC0" - "\uABE2",
+			"\uABF0" - "\uABF9",
+			"\uAC00" - "\uD7A3",
+			"\uD7B0" - "\uD7C6",
+			"\uD7CB" - "\uD7FB",
+			"\uF900" - "\uFB06",
+			"\uFB13" - "\uFB17",
+			"\uFB1D",
+			"\uFB1F" - "\uFB28",
+			"\uFB2A" - "\uFB36",
+			"\uFB38" - "\uFB3C",
+			"\uFB3E",
+			"\uFB40" - "\uFB41",
+			"\uFB43" - "\uFB44",
+			"\uFB46" - "\uFBB1",
+			"\uFBD3" - "\uFD3D",
+			"\uFD50" - "\uFD8F",
+			"\uFD92" - "\uFDC7",
+			"\uFDF0" - "\uFDFB",
+			"\uFE70" - "\uFE74",
+			"\uFE76" - "\uFEFC",
+			"\uFF10" - "\uFF19",
+			"\uFF21" - "\uFF3A",
+			"\uFF41" - "\uFF5A",
+			"\uFF66" - "\uFFBE",
+			"\uFFC2" - "\uFFC7",
+			"\uFFCA" - "\uFFCF",
+			"\uFFD2" - "\uFFD7",
+			"\uFFDA" - "\uFFDC"
         ]
     >
     |
@@ -1569,7 +1569,7 @@ TOKEN:
     |
     <EMPTY_DIRECTIVE_END : "/>" | "/]">
     {
-        if (tagSyntaxEstablished && (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_28
+        if (tagSyntaxEstablished && (incompatibleImprovements >= _VersionInts.V_2_3_28
                 || interpolationSyntax == SQUARE_BRACKET_INTERPOLATION_SYNTAX)) {
             String image = matchedToken.image;
             char lastChar = image.charAt(image.length() - 1);
@@ -3518,11 +3518,11 @@ Macro Macro() :
         // To prevent parser check loopholes like <#list ...><#macro ...><#break></#macro></#list>.
         lastIteratorBlockContexts = iteratorBlockContexts;
         iteratorBlockContexts = null;
-        if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_23) {
-	        lastBreakableDirectiveNesting = breakableDirectiveNesting;
-	        lastContinuableDirectiveNesting = continuableDirectiveNesting;
-	        breakableDirectiveNesting = 0; 
-	        continuableDirectiveNesting = 0;
+        if (incompatibleImprovements >= _VersionInts.V_2_3_23) {
+            lastBreakableDirectiveNesting = breakableDirectiveNesting;
+            lastContinuableDirectiveNesting = continuableDirectiveNesting;
+            breakableDirectiveNesting = 0;
+            continuableDirectiveNesting = 0;
         } else {
             lastBreakableDirectiveNesting = 0; // Just to prevent uninitialized local variable error later
             lastContinuableDirectiveNesting = 0;
@@ -3542,11 +3542,11 @@ Macro Macro() :
     )
     {
         iteratorBlockContexts = lastIteratorBlockContexts;
-        if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_23) {
+        if (incompatibleImprovements >= _VersionInts.V_2_3_23) {
             breakableDirectiveNesting = lastBreakableDirectiveNesting;
             continuableDirectiveNesting = lastContinuableDirectiveNesting;
         }
-        
+
         inMacro = inFunction = false;
         Macro result = new Macro(
                 name, paramNamesWithDefault, catchAllParamName, isFunction, requireArgsSpecialVariable, children);
@@ -4305,7 +4305,7 @@ Token UnparsedContent(Token start, StringBuilder buf) :
     {
         buf.setLength(buf.length() - t.image.length());
         if (!t.image.endsWith(";")
-                && _TemplateAPI.getTemplateLanguageVersionAsInt(template) >= _TemplateAPI.VERSION_INT_2_3_21) {
+                && _TemplateAPI.getTemplateLanguageVersionAsInt(template) >= _VersionInts.V_2_3_21) {
             throw new ParseException("Unclosed \"" + start.image + "\"", template, start);
         }
         return t;
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 9bf0609d..a3adc5ba 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -334,7 +334,7 @@ public class ConfigurationTest extends TestCase {
     
     public void testVersion() {
         Version v = Configuration.getVersion();
-        assertTrue(v.intValue() > _TemplateAPI.VERSION_INT_2_3_20);
+        assertTrue(v.intValue() > _VersionInts.V_2_3_20);
         assertSame(v.toString(), Configuration.getVersionNumber());
         
         try {
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index 17c67023..541bf1bf 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -106,7 +106,7 @@ public class DefaultObjectWrapperTest {
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.32
 
         List<Version> actual = new ArrayList<>();
-        for (int i = _TemplateAPI.VERSION_INT_2_3_0; i <= Configuration.getVersion().intValue(); i++) {
+        for (int i = _VersionInts.V_2_3_0; i <= Configuration.getVersion().intValue(); i++) {
             int major = i / 1000000;
             int minor = i % 1000000 / 1000;
             int micro = i % 1000;
diff --git a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
index f0cb148d..5e179a25 100644
--- a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
@@ -74,7 +74,7 @@ import freemarker.template.TemplateNodeModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
 import freemarker.template.Version;
-import freemarker.template._TemplateAPI;
+import freemarker.template._VersionInts;
 import freemarker.template.utility.NullArgumentException;
 import freemarker.template.utility.NullWriter;
 import freemarker.template.utility.StringUtil;
@@ -450,7 +450,7 @@ public class TemplateTestCase extends FileTestCase {
             dataModel.put("dow", dow);
             dataModel.put("dowPre22", dow
                     && ((DefaultObjectWrapper) conf.getObjectWrapper()).getIncompatibleImprovements()
-                            .intValue() < _TemplateAPI.VERSION_INT_2_3_22);
+                            .intValue() < _VersionInts.V_2_3_22);
         }
     }
     
diff --git a/src/test/java/freemarker/test/templatesuite/models/LegacyList.java b/src/test/java/freemarker/test/templatesuite/models/LegacyList.java
index a02199bc..e23b60bd 100644
--- a/src/test/java/freemarker/test/templatesuite/models/LegacyList.java
+++ b/src/test/java/freemarker/test/templatesuite/models/LegacyList.java
@@ -24,7 +24,7 @@ import java.util.Iterator;
 import freemarker.template.SimpleSequence;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 
 /**
  * A little bridge class that subclasses the new SimpleList
@@ -35,7 +35,7 @@ public class LegacyList extends SimpleSequence {
     private Iterator iterator;
 
     public LegacyList() {
-        super(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+        super(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
     }
 
     /**
diff --git a/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java b/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
index f317b18c..5d4049c6 100644
--- a/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
+++ b/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
@@ -24,7 +24,7 @@ import freemarker.template.TemplateHashModel;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
+import freemarker.template._ObjectWrappers;
 import freemarker.template.utility.HtmlEscape;
 import freemarker.template.utility.StandardCompress;
 
@@ -34,7 +34,7 @@ import freemarker.template.utility.StandardCompress;
 public class TransformHashWrapper implements TemplateHashModel,
         TemplateScalarModel {
 
-    private SimpleHash m_cHashModel = new SimpleHash(_TemplateAPI.SAFE_OBJECT_WRAPPER);
+    private SimpleHash m_cHashModel = new SimpleHash(_ObjectWrappers.SAFE_OBJECT_WRAPPER);
 
     /** Creates new TransformHashWrapper */
     public TransformHashWrapper() {