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 2017/03/17 21:19:49 UTC

[1/4] incubator-freemarker git commit: Made DefaultObjectWrapper immtutable (no more setters), also removed public constructors. Instead, instances are created with DefaultObjectWrapper.Builder.build(). Reworked DefaultObjectWrapper and ClassIntrospector

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 529cdd86f -> dceec32ed


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithShortedMethods.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithShortedMethods.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithShortedMethods.java
deleted file mode 100644
index 2074b0b..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithShortedMethods.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
-
-/**
- * Used so that the order in which the methods are added to the introspection cache is deterministic. 
- */
-public abstract class DefaultObjectWrapperWithShortedMethods extends DefaultObjectWrapper {
-    
-    public DefaultObjectWrapperWithShortedMethods(boolean desc) {
-        super(Configuration.VERSION_3_0_0);
-        setMethodSorter(new AlphabeticalMethodSorter(desc));
-    }
-
-    public DefaultObjectWrapperWithShortedMethods(Version incompatibleImprovements, boolean desc) {
-        super(incompatibleImprovements);
-        setMethodSorter(new AlphabeticalMethodSorter(desc));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithSortedMethods.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithSortedMethods.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithSortedMethods.java
deleted file mode 100644
index d7b74c3..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithSortedMethods.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
-
-public class DefaultObjectWrapperWithSortedMethods extends DefaultObjectWrapper {
-    
-    public DefaultObjectWrapperWithSortedMethods(boolean desc) {
-        this(Configuration.VERSION_3_0_0, desc);
-    }
-
-    public DefaultObjectWrapperWithSortedMethods(Version incompatibleImprovements, boolean desc) {
-        super(incompatibleImprovements);
-        setMethodSorter(this, desc);
-    }
-    
-    static void setMethodSorter(DefaultObjectWrapper ow, boolean desc) {
-        ow.setMethodSorter(new AlphabeticalMethodSorter(desc));
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/EnumModelsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/EnumModelsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/EnumModelsTest.java
index 3d7a585..fc19bb7 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/EnumModelsTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/EnumModelsTest.java
@@ -38,7 +38,8 @@ public class EnumModelsTest {
     
     @Test
     public void modelCaching() throws Exception {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).usePrivateCaches(true)
+                .build();
         TemplateHashModel enums = ow.getEnumModels();
         TemplateHashModel e = (TemplateHashModel) enums.get(E.class.getName());
         assertNotNull(e);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java b/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java
index fc5a34a..4306a50 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java
@@ -38,7 +38,7 @@ public class ErrorMessagesTest {
 
     @Test
     public void getterMessage() throws TemplateModelException {
-        DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         TemplateHashModel thm= (TemplateHashModel) ow.wrap(new TestBean());
         
         try {
@@ -56,7 +56,7 @@ public class ErrorMessagesTest {
     public void markupOutputParameter() throws Exception {
         TemplateHTMLOutputModel html = HTMLOutputFormat.INSTANCE.fromMarkup("<p>a");
 
-        DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         TemplateHashModel thm = (TemplateHashModel) ow.wrap(new TestBean());
         
         {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java b/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
index 2467f17..92f59fb 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
@@ -36,9 +36,10 @@ public class FineTuneMethodAppearanceTest {
 
     @Test
     public void newWayOfConfiguring() throws TemplateModelException {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
-        ow.setMethodAppearanceFineTuner(GetlessMethodsAsPropertyGettersRule.INSTANCE);
-        ow.setExposeFields(true);
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                .methodAppearanceFineTuner(GetlessMethodsAsPropertyGettersRule.INSTANCE)
+                .exposeFields(true)
+                .build();
         checkIfProperlyWrapped(ow.wrap(new C()));
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java b/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java
index 98a3090..96c1adf 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java
@@ -43,7 +43,7 @@ public class Java7MembersOnlyDefaultObjectWrapper extends DefaultObjectWrapper {
     private static final Set<String> POST_JAVA_7_COLLECTION_METHODS = newHashSet("parallelStream", "removeIf", "stream");
     private static final Set<String> POST_JAVA_7_LIST_METHODS = newHashSet("sort", "spliterator");
     
-    MethodAppearanceFineTuner POST_JAVA_7_FILTER = new MethodAppearanceFineTuner() {
+    static final MethodAppearanceFineTuner POST_JAVA_7_FILTER = new MethodAppearanceFineTuner() {
 
         @Override
         public void process(DecisionInput in, Decision out) {
@@ -83,8 +83,7 @@ public class Java7MembersOnlyDefaultObjectWrapper extends DefaultObjectWrapper {
     };
     
     public Java7MembersOnlyDefaultObjectWrapper(Version version) {
-        super(version);
-        setMethodAppearanceFineTuner(POST_JAVA_7_FILTER);
+        super(new DefaultObjectWrapper.Builder(version).methodAppearanceFineTuner(POST_JAVA_7_FILTER), true);
     }
 
     private static <T> Set<T> newHashSet(T... items) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
index 57b8810..495f3f9 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
@@ -47,7 +47,7 @@ public class Java8DefaultObjectWrapperBridgeMethodsTest {
     }
 
     private void test(Class<?> pClass) throws TemplateModelException {
-        DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         TemplateHashModel wrapped;
         try {
             wrapped = (TemplateHashModel) ow.wrap(pClass.newInstance());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java b/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
index c4fe82f..905d536 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
@@ -36,7 +36,7 @@ public class Java8DefaultObjectWrapperTest {
 
     @Test
     public void testDefaultMethodRecognized() throws TemplateModelException {
-        DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+        DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
         DefaultObjectWrapper ow = owb.build();
         TemplateHashModel wrappedBean = (TemplateHashModel) ow.wrap(new Java8DefaultMethodsBean());
         

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java b/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java
index 50a3e2f..13cd79b 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java
@@ -26,7 +26,7 @@ import junit.framework.TestCase;
 
 public class ModelAPINewInstanceTest extends TestCase {
 
-    private DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+    private DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
 
     public ModelAPINewInstanceTest(String name) {
         super(name);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java b/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java
index 08dfe7c..2498859 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java
@@ -33,7 +33,7 @@ public class ModelCacheTest {
     
     @Test
     public void modelCacheOff() throws Exception {
-        DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         assertFalse(ow.getUseModelCache());  // default is off
         
         String s = "foo";
@@ -46,8 +46,8 @@ public class ModelCacheTest {
     @Test
     @Ignore // ModelCache is current removed in FM3
     public void modelCacheOn() throws Exception {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
-        ow.setUseModelCache(true);
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                .useModelCache(true).build();
         assertTrue(ow.getUseModelCache());
 
         TestBean obj = new TestBean();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/StaticModelsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/StaticModelsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/StaticModelsTest.java
index 54cedf3..609d632 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/StaticModelsTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/StaticModelsTest.java
@@ -39,7 +39,8 @@ public class StaticModelsTest {
 
     @Test
     public void modelCaching() throws Exception {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).usePrivateCaches(true)
+                .build();
         TemplateHashModel statics = ow.getStaticModels();
         TemplateHashModel s = (TemplateHashModel) statics.get(S.class.getName());
         assertNotNull(s);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/TypeFlagsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/TypeFlagsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/TypeFlagsTest.java
index 701ba34..fe11d1c 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/TypeFlagsTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/TypeFlagsTest.java
@@ -44,7 +44,7 @@ public class TypeFlagsTest extends TestCase {
         super(name);
     }
     
-    private final DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+    private final DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
 
     public void testSingleNumType() {
         checkTypeFlags(SingleNumTypeC.class, "mInt",

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java b/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java
index 5cb3400..0b5861d 100644
--- a/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java
+++ b/src/test/java/org/apache/freemarker/core/templateresolver/FileTemplateLoaderTest.java
@@ -47,7 +47,7 @@ public class FileTemplateLoaderTest {
         File sub1Dir = new File(templateRootDir, "sub1");
         File sub2Dir = new File(sub1Dir, "sub2");
         if (!sub2Dir.mkdirs()) {
-            throw new IOException("Failed to create subdirectories");
+            throw new IOException("Failed to invoke subdirectories");
         }
         File tFile = new File(sub2Dir, "t.ftl");
         FileUtils.write(tFile, "foo");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java b/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
index 4f87e2c..34d00ec 100644
--- a/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
@@ -42,7 +42,6 @@ import org.apache.freemarker.core.TemplateExceptionHandler;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
@@ -411,7 +410,7 @@ public class RealServletContainertTest extends WebAppTestCase {
             Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
             cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
             cfg.setLogTemplateExceptions(true);
-            DefaultObjectWrapperBuilder bwb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper.Builder bwb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             bwb.setUseModelCache(true);
             cfg.setObjectWrapper(bwb.build());
             cfg.setTemplateLoader(new WebAppTemplateLoader(getServletContext()));
@@ -453,7 +452,7 @@ public class RealServletContainertTest extends WebAppTestCase {
 
         @Override
         protected ObjectWrapperAndUnwrapper createDefaultObjectWrapper() {
-            DefaultObjectWrapperBuilder bwb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper.Builder bwb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             bwb.setUseModelCache(true);
             assertEquals(Configuration.VERSION_3_0_0, bwb.getIncompatibleImprovements());
             return bwb.build();
@@ -467,7 +466,7 @@ public class RealServletContainertTest extends WebAppTestCase {
         @Override
         protected Configuration createConfiguration() {
             Configuration cfg = new Configuration(Configuration.VERSION_3_0_0);
-            cfg.setObjectWrapper(new RestrictedObjectWrapper(Configuration.VERSION_3_0_0));
+            cfg.setObjectWrapper(new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
             return cfg;
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/servlet/jsp/TLDParsingTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/servlet/jsp/TLDParsingTest.java b/src/test/java/org/apache/freemarker/servlet/jsp/TLDParsingTest.java
index a1af23e..c066070 100644
--- a/src/test/java/org/apache/freemarker/servlet/jsp/TLDParsingTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/jsp/TLDParsingTest.java
@@ -36,7 +36,6 @@ import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.model.TemplateMethodModelEx;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.SimpleScalar;
 import org.junit.Before;
 import org.junit.Test;
@@ -52,7 +51,7 @@ public class TLDParsingTest {
 
     @Before
     public void before() throws Exception {
-        wrapper = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        wrapper = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/test/ResourcesExtractor.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/ResourcesExtractor.java b/src/test/java/org/apache/freemarker/test/ResourcesExtractor.java
index df37d11..e36d845 100644
--- a/src/test/java/org/apache/freemarker/test/ResourcesExtractor.java
+++ b/src/test/java/org/apache/freemarker/test/ResourcesExtractor.java
@@ -159,7 +159,7 @@ public final class ResourcesExtractor {
         File dstFile = new File(dstRootDir, contDstPath);
         if (contLine.endsWith("/")) {
             if (!dstFile.mkdirs()) {
-                throw new IOException("Failed to create directory: " + dstFile);
+                throw new IOException("Failed to invoke directory: " + dstFile);
             }
         } else {
             String srcEntryPath = contSrcPathRelative ? srcDirResourcePath + contSrcPath : contSrcPath;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java b/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java
index df6fd4c..1580932 100644
--- a/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java
+++ b/src/test/java/org/apache/freemarker/test/servlet/WebAppTestCase.java
@@ -302,7 +302,7 @@ public class WebAppTestCase {
                 FileUtils.deleteDirectory(d);
             }
             if (!d.mkdirs()) {
-               throw new IOException("Failed to create Jetty temp directory: " + d); 
+               throw new IOException("Failed to invoke Jetty temp directory: " + d);
             }
             testTempDirectory = d;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
index 347ae06..eea08f2 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
@@ -56,7 +56,6 @@ import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.impl.DefaultNonListCollectionAdapter;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.ResourceBundleModel;
 import org.apache.freemarker.core.model.impl.SimpleCollection;
 import org.apache.freemarker.core.model.impl.SimpleDate;
@@ -179,7 +178,7 @@ public class TemplateTestCase extends FileTestCase {
         conf.setTemplateLoader(new CopyrightCommentRemoverTemplateLoader(
                 new FileTemplateLoader(new File(getTestClassDirectory(), "templates"))));
         
-        DefaultObjectWrapper dow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         
         dataModel.put(ASSERT_VAR_NAME, AssertDirective.INSTANCE);
         dataModel.put(ASSERT_EQUALS_VAR_NAME, AssertEqualsDirective.INSTANCE);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/test/templatesuite/models/Listables.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/templatesuite/models/Listables.java b/src/test/java/org/apache/freemarker/test/templatesuite/models/Listables.java
index 09b9064..c2d7c90 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/models/Listables.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/models/Listables.java
@@ -38,7 +38,6 @@ import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.WrappingTemplateModel;
 import org.apache.freemarker.core.model.impl.DefaultMapAdapter;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.SimpleCollection;
 import org.apache.freemarker.core.model.impl.SimpleHash;
 
@@ -124,7 +123,7 @@ public class Listables {
     public List<? extends TemplateHashModelEx> getEmptyHashes() throws TemplateModelException {
         List<TemplateHashModelEx> emptyMaps = new ArrayList<>();
         emptyMaps.addAll(getMapsWrappedAsEx2(Collections.emptyMap()));
-        emptyMaps.add((TemplateHashModelEx) new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build()
+        emptyMaps.add((TemplateHashModelEx) new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build()
                 .wrap(Collections.emptyMap()));
         return emptyMaps;
     }
@@ -135,7 +134,7 @@ public class Listables {
     private List<TemplateHashModelEx2> getMapsWrappedAsEx2(Map<?, ?> map) throws TemplateModelException {
         List<TemplateHashModelEx2> maps = new ArrayList<>();
         
-        DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         maps.add(new SimpleHash(map, ow));
         maps.add((DefaultMapAdapter) ow.wrap(map));
 
@@ -144,7 +143,7 @@ public class Listables {
     
     public TemplateHashModelEx getHashNonEx2() {
         return new NonEx2MapAdapter(ImmutableMap.of("k1", 11, "k2", 22),
-                new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build());
+                new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
     }
     
     public static class NonEx2MapAdapter extends WrappingTemplateModel implements TemplateHashModelEx {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/test/templatesuite/models/MultiModel1.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/templatesuite/models/MultiModel1.java b/src/test/java/org/apache/freemarker/test/templatesuite/models/MultiModel1.java
index 3c1adfb..66a0ffe 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/models/MultiModel1.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/models/MultiModel1.java
@@ -26,7 +26,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.model.impl.SimpleHash;
 import org.apache.freemarker.core.model.impl.SimpleScalar;
 import org.apache.freemarker.core.model.impl.SimpleSequence;
@@ -37,7 +37,7 @@ import org.apache.freemarker.core.model.impl.SimpleSequence;
 public class MultiModel1 implements TemplateHashModel,
         TemplateSequenceModel, TemplateScalarModel {
 
-    private ObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+    private ObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
 
     private TemplateModel m_cSubModel = new MultiModel2();
     private TemplateModel m_cListHashModel1 = new MultiModel4(ow);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/test/templatesuite/models/TransformHashWrapper.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/templatesuite/models/TransformHashWrapper.java b/src/test/java/org/apache/freemarker/test/templatesuite/models/TransformHashWrapper.java
index cfdbe12..4602b67 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/models/TransformHashWrapper.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/models/TransformHashWrapper.java
@@ -25,7 +25,7 @@ import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.model.impl.SimpleHash;
 import org.apache.freemarker.core.util.HtmlEscape;
 import org.apache.freemarker.core.util.StandardCompress;
@@ -36,7 +36,7 @@ import org.apache.freemarker.core.util.StandardCompress;
 public class TransformHashWrapper implements TemplateHashModel,
         TemplateScalarModel {
 
-    private ObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+    private ObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
     private SimpleHash m_cHashModel = new SimpleHash(ow);
 
     /** Creates new TransformHashWrapper */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/test/util/SimpleMapAndCollectionObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/util/SimpleMapAndCollectionObjectWrapper.java b/src/test/java/org/apache/freemarker/test/util/SimpleMapAndCollectionObjectWrapper.java
index 16d75db..c1da1c4 100644
--- a/src/test/java/org/apache/freemarker/test/util/SimpleMapAndCollectionObjectWrapper.java
+++ b/src/test/java/org/apache/freemarker/test/util/SimpleMapAndCollectionObjectWrapper.java
@@ -36,7 +36,7 @@ import org.apache.freemarker.core.model.impl.SimpleSequence;
 public class SimpleMapAndCollectionObjectWrapper extends DefaultObjectWrapper {
 
     public SimpleMapAndCollectionObjectWrapper(Version incompatibleImprovements) {
-        super(incompatibleImprovements);
+        super(new DefaultObjectWrapper.Builder(incompatibleImprovements), true);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/test/util/XMLLoader.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/util/XMLLoader.java b/src/test/java/org/apache/freemarker/test/util/XMLLoader.java
index 914fd0d..90763a0 100644
--- a/src/test/java/org/apache/freemarker/test/util/XMLLoader.java
+++ b/src/test/java/org/apache/freemarker/test/util/XMLLoader.java
@@ -44,7 +44,7 @@ public final class XMLLoader {
     }
     
     /**
-     * Convenience method to create a {@link NodeModel} from a SAX {@link InputSource}.
+     * Convenience method to invoke a {@link NodeModel} from a SAX {@link InputSource}.
      */
     static public NodeModel toModel(InputSource is, boolean simplify)
         throws SAXException, IOException, ParserConfigurationException {


[3/4] incubator-freemarker git commit: Made DefaultObjectWrapper immtutable (no more setters), also removed public constructors. Instead, instances are created with DefaultObjectWrapper.Builder.build(). Reworked DefaultObjectWrapper and ClassIntrospector

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
index f5c9a2c..da6c675 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
@@ -19,6 +19,8 @@
 
 package org.apache.freemarker.core.model.impl;
 
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
@@ -36,6 +38,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.WeakHashMap;
 
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Version;
@@ -61,30 +64,28 @@ import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
 import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util.WriteProtectable;
+import org.apache.freemarker.core.util.BuilderBase;
 import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.dom.NodeModel;
 import org.slf4j.Logger;
 import org.w3c.dom.Node;
 
 /**
- * The default implementation of the {@link ObjectWrapper} interface. Usually, you don't need to create instances of
+ * The default implementation of the {@link ObjectWrapper} interface. Usually, you don't need to invoke instances of
  * this, as an instance of this is already the default value of the
  * {@link Configuration#setObjectWrapper(ObjectWrapper) object_wrapper setting}. Then the
- * {@link #DefaultObjectWrapper(Version) incompatibleImprovements} of the {@link DefaultObjectWrapper} will be the
- * same that you have set for the {@link Configuration} itself.
+ * {@link ExtendableBuilder#ExtendableBuilder(Version, boolean) incompatibleImprovements} of the
+ * {@link DefaultObjectWrapper} will be the same that you have set for the {@link Configuration} itself.
  * 
  * <p>
- * If you still need to create an instance, that should be done with an {@link DefaultObjectWrapperBuilder} (or
- * with {@link Configuration#setSetting(String, String)} with {@code "object_wrapper"} key), not with
- * its constructor, as that allows FreeMarker to reuse singletons.
+ * If you still need to invoke an instance, that should be done with {@link Builder#build()} (or
+ * with {@link Configuration#setSetting(String, String)} with {@code "objectWrapper"} key); the constructor isn't
+ * public.
  *
  * <p>
- * This class is only thread-safe after you have finished calling its setter methods, and then safely published it (see
- * JSR 133 and related literature). When used as part of {@link Configuration}, of course it's enough if that was safely
- * published and then left unmodified.
+ * This class is thread-safe.
  */
-public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable {
+public class DefaultObjectWrapper implements RichObjectWrapper {
 
     private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
 
@@ -118,9 +119,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
      * At this level of exposure, no bean properties and methods are exposed.
      * Only map items, resource bundle items, and objects retrieved through
      * the generic get method (on objects of classes that have a generic get
-     * method) can be retrieved through the hash interface. You might want to
-     * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to
-     * speed up map item retrieval.
+     * method) can be retrieved through the hash interface.
      */
     public static final int EXPOSE_NOTHING = 3;
 
@@ -133,12 +132,10 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
      * {@link Class} to class info cache.
      * This object is possibly shared with other {@link DefaultObjectWrapper}-s!
      *
-     * <p>To write this, always use {@link #replaceClassIntrospector(ClassIntrospectorBuilder)}.
-     *
      * <p>When reading this, it's good idea to synchronize on sharedInrospectionLock when it doesn't hurt overall
      * performance. In theory that's not needed, but apps might fail to keep the rules.
      */
-    private ClassIntrospector classIntrospector;
+    private final ClassIntrospector classIntrospector;
 
     /**
      * {@link String} class name to {@link StaticModel} cache.
@@ -158,138 +155,56 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
 
     // -----------------------------------------------------------------------------------------------------------------
 
-    // Why volatile: In principle it need not be volatile, but we want to catch modification attempts even if the
-    // object was published improperly to other threads. After all, the main goal of WriteProtectable is protecting
-    // things from buggy user code.
-    private volatile boolean writeProtected;
-
-    private int defaultDateType; // initialized by PropertyAssignments.apply
-    private ObjectWrapper outerIdentity = this;
-    private boolean methodsShadowItems = true;
-    private boolean strict;  // initialized by PropertyAssignments.apply
+    private final int defaultDateType;
+    private final ObjectWrapper outerIdentity;
+    private final boolean strict;
     @Deprecated // Only exists to keep some JUnit tests working... [FM3]
-    private boolean useModelCache;
+    private final boolean useModelCache;
 
     private final Version incompatibleImprovements;
 
     /**
-     * Use {@link DefaultObjectWrapperBuilder} instead of the public constructors if possible.
-     * The main disadvantage of using the public constructors is that the instances won't share caches. So unless having
-     * a private cache is your goal, don't use them. See
-     *
-     * @param incompatibleImprovements
-     *   Sets which of the non-backward-compatible improvements should be enabled. Not {@code null}. This version number
-     *   is the same as the FreeMarker version number with which the improvements were implemented.
-     *
-     *   <p>For new projects, it's recommended to set this to the FreeMarker version that's used during the development.
-     *   For released products that are still actively developed it's a low risk change to increase the 3rd
-     *   version number further as FreeMarker is updated, but of course you should always check the list of effects
-     *   below. Increasing the 2nd or 1st version number possibly mean substantial changes with higher risk of breaking
-     *   the application, but again, see the list of effects below.
-     *
-     *   <p>The reason it's separate from {@link Configuration#setIncompatibleImprovements(Version)} is that
-     *   {@link ObjectWrapper} objects are often shared among multiple {@link Configuration}-s, so the two version
-     *   numbers are technically independent. But it's recommended to keep those two version numbers the same.
-     *
-     *   <p>The changes enabled by {@code incompatibleImprovements} are:
-     *   <ul>
-     *     <li>
-     *       <p>2.3.0: No changes; this is the starting point, the version used in older projects.
-     *     </li>
-     *     <li>
-     *       <p>2.3.21 (or higher):
-     *       Several glitches were oms in <em>overloaded</em> method selection. This usually just gets
-     *       rid of errors (like ambiguity exceptions and numerical precision loses due to bad overloaded method
-     *       choices), still, as in some cases the method chosen can be a different one now (that was the point of
-     *       the reworking after all), it can mean a change in the behavior of the application. The most important
-     *       change is that the treatment of {@code null} arguments were oms, as earlier they were only seen
-     *       applicable to parameters of type {@code Object}. Now {@code null}-s are seen to be applicable to any
-     *       non-primitive parameters, and among those the one with the most specific type will be preferred (just
-     *       like in Java), which is hence never the one with the {@code Object} parameter type. For more details
-     *       about overloaded method selection changes see the version history in the FreeMarker Manual.
-     *     </li>
-     *     <li>
-     *       <p>2.3.24 (or higher):
-     *       {@link Iterator}-s were always said to be non-empty when using {@code ?has_content} and such (i.e.,
-     *       operators that check emptiness without reading any elements). Now an {@link Iterator} counts as
-     *       empty exactly if it has no elements left. (Note that this bug has never affected basic functionality, like
-     *       {@code <#list ...>}.)
-     *     </li>
-     *   </ul>
-     *
-     *   <p>Note that the version will be normalized to the lowest version where the same incompatible
-     *   {@link DefaultObjectWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might returns
-     *   a lower version than what you have specified.
-     *
-     * @since 2.3.21
-     */
-    public DefaultObjectWrapper(Version incompatibleImprovements) {
-        this(new DefaultObjectWrapperConfiguration(incompatibleImprovements) {}, false);
-        // Attention! Don't don anything here, as the instance is possibly already visible to other threads through the
-        // model factory callbacks.
-    }
-
-    /**
-     * Same as {@link #DefaultObjectWrapper(DefaultObjectWrapperConfiguration, boolean, boolean)} with {@code true}
-     * {@code finalizeConstruction} argument.
-     *
-     * @since 2.3.21
-     */
-    protected DefaultObjectWrapper(DefaultObjectWrapperConfiguration bwConf, boolean writeProtected) {
-        this(bwConf, writeProtected, true);
-    }
-
-    /**
-     * Initializes the instance based on the the {@link DefaultObjectWrapperConfiguration} specified.
-     *
-     * @param writeProtected Makes the instance's configuration settings read-only via
-     *     {@link WriteProtectable#writeProtect()}; this way it can use the shared class introspection cache.
+     * Initializes the instance based on the the {@link ExtendableBuilder} specified.
      *
      * @param finalizeConstruction Decides if the construction is finalized now, or the caller will do some more
-     *     adjustments on the instance and then call {@link #finalizeConstruction(boolean)} itself.
-     *
-     * @since 2.3.22
+     *     adjustments on the instance and then call {@link #finalizeConstruction()} itself.
      */
-    protected DefaultObjectWrapper(DefaultObjectWrapperConfiguration bwConf, boolean writeProtected, boolean finalizeConstruction) {
-        incompatibleImprovements = bwConf.getIncompatibleImprovements();  // normalized
+    protected DefaultObjectWrapper(ExtendableBuilder builder, boolean finalizeConstruction) {
+        incompatibleImprovements = builder.getIncompatibleImprovements();  // normalized
 
-        defaultDateType = bwConf.getDefaultDateType();
-        outerIdentity = bwConf.getOuterIdentity() != null ? bwConf.getOuterIdentity() : this;
-        strict = bwConf.isStrict();
+        defaultDateType = builder.getDefaultDateType();
+        outerIdentity = builder.getOuterIdentity() != null ? builder.getOuterIdentity() : this;
+        strict = builder.isStrict();
 
-        if (!writeProtected) {
+        if (builder.getUsePrivateCaches()) {
             // As this is not a read-only DefaultObjectWrapper, the classIntrospector will be possibly replaced for a few times,
             // but we need to use the same sharedInrospectionLock forever, because that's what the model factories
             // synchronize on, even during the classIntrospector is being replaced.
             sharedIntrospectionLock = new Object();
-            classIntrospector = new ClassIntrospector(bwConf.classIntrospectorBuilder, sharedIntrospectionLock);
+            classIntrospector = new ClassIntrospector(builder.classIntrospectorBuilder, sharedIntrospectionLock);
         } else {
             // As this is a read-only DefaultObjectWrapper, the classIntrospector is never replaced, and since it's shared by
             // other DefaultObjectWrapper instances, we use the lock belonging to the shared ClassIntrospector.
-            classIntrospector = bwConf.classIntrospectorBuilder.build();
+            classIntrospector = builder.classIntrospectorBuilder.build();
             sharedIntrospectionLock = classIntrospector.getSharedLock();
         }
 
         staticModels = new StaticModels(this);
         enumModels = new EnumModels(this);
-        setUseModelCache(bwConf.getUseModelCache());
+        useModelCache = builder.getUseModelCache();
 
-        finalizeConstruction(writeProtected);
+        finalizeConstruction();
     }
 
     /**
-     * Meant to be called after {@link DefaultObjectWrapper#DefaultObjectWrapper(DefaultObjectWrapperConfiguration, boolean, boolean)} when
+     * Meant to be called after {@link DefaultObjectWrapper#DefaultObjectWrapper(ExtendableBuilder, boolean)} when
      * its last argument was {@code false}; makes the instance read-only if necessary, then registers the model
      * factories in the class introspector. No further changes should be done after calling this, if
      * {@code writeProtected} was {@code true}.
      *
      * @since 2.3.22
      */
-    protected void finalizeConstruction(boolean writeProtected) {
-        if (writeProtected) {
-            writeProtect();
-        }
-
+    protected void finalizeConstruction() {
         // Attention! At this point, the DefaultObjectWrapper must be fully initialized, as when the model factories are
         // registered below, the DefaultObjectWrapper can immediately get concurrent callbacks. That those other threads will
         // see consistent image of the DefaultObjectWrapper is ensured that callbacks are always sync-ed on
@@ -298,94 +213,20 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
         registerModelFactories();
     }
 
-    /**
-     * Makes the configuration properties (settings) of this {@link DefaultObjectWrapper} object read-only. As changing them
-     * after the object has become visible to multiple threads leads to undefined behavior, it's recommended to call
-     * this when you have finished configuring the object.
-     *
-     * <p>Consider using {@link DefaultObjectWrapperBuilder} instead, which gives an instance that's already
-     * write protected and also uses some shared caches/pools.
-     *
-     * @since 2.3.21
-     */
-    @Override
-    public void writeProtect() {
-        writeProtected = true;
-    }
-
-    /**
-     * @since 2.3.21
-     */
-    @Override
-    public boolean isWriteProtected() {
-        return writeProtected;
-    }
-
     Object getSharedIntrospectionLock() {
         return sharedIntrospectionLock;
     }
 
     /**
-     * If this object is already read-only according to {@link WriteProtectable}, throws {@link IllegalStateException},
-     * otherwise does nothing.
-     *
-     * @since 2.3.21
-     */
-    protected void checkModifiable() {
-        if (writeProtected) throw new IllegalStateException(
-                "Can't modify the " + getClass().getName() + " object, as it was write protected.");
-    }
-
-    /**
-     * @see #setStrict(boolean)
+     * @see ExtendableBuilder#setStrict(boolean)
      */
     public boolean isStrict() {
         return strict;
     }
 
     /**
-     * Specifies if an attempt to read a bean property that doesn't exist in the
-     * wrapped object should throw an {@link InvalidPropertyException}.
-     *
-     * <p>If this property is <tt>false</tt> (the default) then an attempt to read
-     * a missing bean property is the same as reading an existing bean property whose
-     * value is <tt>null</tt>. The template can't tell the difference, and thus always
-     * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins
-     * to handle the situation.
-     *
-     * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in
-     * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean
-     * object (as opposed to just holding <tt>null</tt> value) will cause
-     * {@link InvalidPropertyException}, which can't be suppressed in the template
-     * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way
-     * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to
-     * handle existing properties whose value is <tt>null</tt>, without the risk of
-     * hiding typos in the property names. Typos will always cause error. But mind you, it
-     * goes against the basic approach of FreeMarker, so use this feature only if you really
-     * know what you are doing.
-     */
-    public void setStrict(boolean strict) {
-        checkModifiable();
-        this.strict = strict;
-    }
-
-    /**
-     * When wrapping an object, the DefaultObjectWrapper commonly needs to wrap
-     * "sub-objects", for example each element in a wrapped collection.
-     * Normally it wraps these objects using itself. However, this makes
-     * it difficult to delegate to a DefaultObjectWrapper as part of a custom
-     * aggregate ObjectWrapper. This method lets you set the ObjectWrapper
-     * which will be used to wrap the sub-objects.
-     * @param outerIdentity the aggregate ObjectWrapper
-     */
-    public void setOuterIdentity(ObjectWrapper outerIdentity) {
-        checkModifiable();
-        this.outerIdentity = outerIdentity;
-    }
-
-    /**
      * By default returns <tt>this</tt>.
-     * @see #setOuterIdentity(ObjectWrapper)
+     * @see ExtendableBuilder#setOuterIdentity(ObjectWrapper)
      */
     public ObjectWrapper getOuterIdentity() {
         return outerIdentity;
@@ -417,21 +258,6 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
     */
 
     /**
-     * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
-     * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
-     * constants.
-     */
-    public void setExposureLevel(int exposureLevel) {
-        checkModifiable();
-
-        if (classIntrospector.getExposureLevel() != exposureLevel) {
-            ClassIntrospectorBuilder builder = classIntrospector.createBuilder();
-            builder.setExposureLevel(exposureLevel);
-            replaceClassIntrospector(builder);
-        }
-    }
-
-    /**
      * @since 2.3.21
      */
     public int getExposureLevel() {
@@ -439,28 +265,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
     }
 
     /**
-     * Controls whether public instance fields of classes are exposed to
-     * templates.
-     * @param exposeFields if set to true, public instance fields of classes
-     * that do not have a property getter defined can be accessed directly by
-     * their name. If there is a property getter for a property of the same
-     * name as the field (i.e. getter "getFoo()" and field "foo"), then
-     * referring to "foo" in template invokes the getter. If set to false, no
-     * access to public instance fields of classes is given. Default is false.
-     */
-    public void setExposeFields(boolean exposeFields) {
-        checkModifiable();
-
-        if (classIntrospector.getExposeFields() != exposeFields) {
-            ClassIntrospectorBuilder builder = classIntrospector.createBuilder();
-            builder.setExposeFields(exposeFields);
-            replaceClassIntrospector(builder);
-        }
-    }
-
-    /**
      * Returns whether exposure of public instance fields of classes is
-     * enabled. See {@link #setExposeFields(boolean)} for details.
+     * enabled. See {@link ExtendableBuilder#setExposeFields(boolean)} for details.
      * @return true if public instance fields are exposed, false otherwise.
      */
     public boolean isExposeFields() {
@@ -471,39 +277,15 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
         return classIntrospector.getMethodAppearanceFineTuner();
     }
 
-    /**
-     * Used to tweak certain aspects of how methods appear in the data-model;
-     * see {@link MethodAppearanceFineTuner} for more.
-     */
-    public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) {
-        checkModifiable();
-
-        if (classIntrospector.getMethodAppearanceFineTuner() != methodAppearanceFineTuner) {
-            ClassIntrospectorBuilder builder = classIntrospector.createBuilder();
-            builder.setMethodAppearanceFineTuner(methodAppearanceFineTuner);
-            replaceClassIntrospector(builder);
-        }
-    }
-
     MethodSorter getMethodSorter() {
         return classIntrospector.getMethodSorter();
     }
 
-    void setMethodSorter(MethodSorter methodSorter) {
-        checkModifiable();
-
-        if (classIntrospector.getMethodSorter() != methodSorter) {
-            ClassIntrospectorBuilder builder = classIntrospector.createBuilder();
-            builder.setMethodSorter(methodSorter);
-            replaceClassIntrospector(builder);
-        }
-    }
-
     /**
      * Tells if this instance acts like if its class introspection cache is sharable with other {@link DefaultObjectWrapper}-s.
      * A restricted cache denies certain too "antisocial" operations, like {@link #clearClassIntrospecitonCache()}.
      * The value depends on how the instance
-     * was created; with a public constructor (then this is {@code false}), or with {@link DefaultObjectWrapperBuilder}
+     * was created; with a public constructor (then this is {@code false}), or with {@link Builder}
      * (then it's {@code true}). Note that in the last case it's possible that the introspection cache
      * will not be actually shared because there's no one to share with, but this will {@code true} even then.
      *
@@ -513,42 +295,6 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
         return classIntrospector.getHasSharedInstanceRestrictons();
     }
 
-    /**
-     * Replaces the value of {@link #classIntrospector}, but first it unregisters
-     * the model factories in the old {@link #classIntrospector}.
-     */
-    private void replaceClassIntrospector(ClassIntrospectorBuilder builder) {
-        checkModifiable();
-
-        final ClassIntrospector newCI = new ClassIntrospector(builder, sharedIntrospectionLock);
-        final ClassIntrospector oldCI;
-
-        // In principle this need not be synchronized, but as apps might publish the configuration improperly, or
-        // even modify the wrapper after publishing. This doesn't give 100% protection from those violations,
-        // as classIntrospector reading aren't everywhere synchronized for performance reasons. It still decreases the
-        // chance of accidents, because some ops on classIntrospector are synchronized, and because it will at least
-        // push the new value into the common shared memory.
-        synchronized (sharedIntrospectionLock) {
-            oldCI = classIntrospector;
-            if (oldCI != null) {
-                // Note that after unregistering the model factory might still gets some callback from the old
-                // classIntrospector
-                if (staticModels != null) {
-                    oldCI.unregisterModelFactory(staticModels);
-                    staticModels.clearCache();
-                }
-                if (enumModels != null) {
-                    oldCI.unregisterModelFactory(enumModels);
-                    enumModels.clearCache();
-                }
-            }
-
-            classIntrospector = newCI;
-
-            registerModelFactories();
-        }
-    }
-
     private void registerModelFactories() {
         if (staticModels != null) {
             classIntrospector.registerModelFactory(staticModels);
@@ -559,47 +305,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
     }
 
     /**
-     * Sets whether methods shadow items in beans. When true (this is the
-     * default value), <code>${object.name}</code> will first try to locate
-     * a bean method or property with the specified name on the object, and
-     * only if it doesn't find it will it try to call
-     * <code>object.get(name)</code>, the so-called "generic get method" that
-     * is usually used to access items of a container (i.e. elements of a map).
-     * When set to false, the lookup order is reversed and generic get method
-     * is called first, and only if it returns null is method lookup attempted.
-     */
-    public void setMethodsShadowItems(boolean methodsShadowItems) {
-        // This sync is here as this method was originally synchronized, but was never truly thread-safe, so I don't
-        // want to advertise it in the javadoc, nor I wanted to break any apps that work because of this accidentally.
-        synchronized (this) {
-            checkModifiable();
-            this.methodsShadowItems = methodsShadowItems;
-        }
-    }
-
-    boolean isMethodsShadowItems() {
-        return methodsShadowItems;
-    }
-
-    /**
-     * Sets the default date type to use for date models that result from
-     * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
-     * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is
-     * {@link TemplateDateModel#UNKNOWN}.
-     * @param defaultDateType the new default date type.
-     */
-    public void setDefaultDateType(int defaultDateType) {
-        // This sync is here as this method was originally synchronized, but was never truly thread-safe, so I don't
-        // want to advertise it in the javadoc, nor I wanted to break any apps that work because of this accidentally.
-        synchronized (this) {
-            checkModifiable();
-
-            this.defaultDateType = defaultDateType;
-        }
-    }
-
-    /**
-     * Returns the default date type. See {@link #setDefaultDateType(int)} for
+     * Returns the default date type. See {@link ExtendableBuilder#setDefaultDateType(int)} for
      * details.
      * @return the default date type
      */
@@ -612,23 +318,14 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
      */
     // [FM3] Remove
     @Deprecated
-    public void setUseModelCache(boolean useCache) {
-        checkModifiable();
-        useModelCache = useCache;
-    }
-
-    /**
-     * @deprecated Does nothing in FreeMarker 3 - we kept it for now to postopne reworking some JUnit tests.
-     */
-    // [FM3] Remove
-    @Deprecated
     public boolean getUseModelCache() {
         return useModelCache;
     }
 
     /**
-     * Returns the version given with {@link #DefaultObjectWrapper(Version)}, normalized to the lowest version where a change
-     * has occurred. Thus, this is not necessarily the same version than that was given to the constructor.
+     * Returns the version given with {@link Builder (Version)}, normalized to the lowest version
+     * where a change has occurred. Thus, this is not necessarily the same version than that was given to the
+     * constructor.
      *
      * @since 2.3.21
      */
@@ -1481,4 +1178,501 @@ public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable
                 + ")";
     }
 
+    /**
+     * Gets/creates a {@link DefaultObjectWrapper} singleton instance that's already configured as specified in the properties of
+     * this object; this is recommended over using the {@link DefaultObjectWrapper} constructors. The returned instance can't be
+     * further configured (it's write protected).
+     *
+     * <p>The builder meant to be used as a drop-away object (not stored in a field), like in this example:
+     * <pre>
+     *    DefaultObjectWrapper dow = new Builder(Configuration.VERSION_2_3_21).build();
+     * </pre>
+     *
+     * <p>Or, a more complex example:</p>
+     * <pre>
+     *    // Create the builder:
+     *    Builder builder = new Builder(Configuration.VERSION_2_3_21);
+     *    // Set desired DefaultObjectWrapper configuration properties:
+     *    builder.setUseModelCache(true);
+     *    builder.setExposeFields(true);
+     *
+     *    // Get the singleton:
+     *    DefaultObjectWrapper dow = builder.build();
+     *    // You don't need the builder anymore.
+     * </pre>
+     *
+     * <p>Despite that builders aren't meant to be used as long-lived objects (singletons), the builder is thread-safe after
+     * you have stopped calling its setters and it was safely published (see JSR 133) to other threads. This can be useful
+     * if you have to put the builder into an IoC container, rather than the singleton it produces.
+     *
+     * <p>The main benefit of using a builder instead of a {@link DefaultObjectWrapper} constructor is that this way the
+     * internal object wrapping-related caches (most notably the class introspection cache) will come from a global,
+     * JVM-level (more precisely, {@code freemarker-core.jar}-class-loader-level) cache. Also the
+     * {@link DefaultObjectWrapper} singletons
+     * themselves are stored in this global cache. Some of the wrapping-related caches are expensive to build and can take
+     * significant amount of memory. Using builders, components that use FreeMarker will share {@link DefaultObjectWrapper}
+     * instances and said caches even if they use separate FreeMarker {@link Configuration}-s. (Many Java libraries use
+     * FreeMarker internally, so {@link Configuration} sharing is not an option.)
+     *
+     * <p>Note that the returned {@link DefaultObjectWrapper} instances are only weak-referenced from inside the builder mechanism,
+     * so singletons are garbage collected when they go out of usage, just like non-singletons.
+     *
+     * <p>About the object wrapping-related caches:
+     * <ul>
+     *   <li><p>Class introspection cache: Stores information about classes that once had to be wrapped. The cache is
+     *     stored in the static fields of certain FreeMarker classes. Thus, if you have two {@link DefaultObjectWrapper}
+     *     instances, they might share the same class introspection cache. But if you have two
+     *     {@code freemarker.jar}-s (typically, in two Web Application's {@code WEB-INF/lib} directories), those won't
+     *     share their caches (as they don't share the same FreeMarker classes).
+     *     Also, currently there's a separate cache for each permutation of the property values that influence class
+     *     introspection: {@link Builder#setExposeFields(boolean) expose_fields} and
+     *     {@link Builder#setExposureLevel(int) exposure_level}. So only {@link DefaultObjectWrapper} where those
+     *     properties are the same may share class introspection caches among each other.
+     *   </li>
+     *   <li><p>Model caches: These are local to a {@link DefaultObjectWrapper}. {@link Builder} returns the same
+     *     {@link DefaultObjectWrapper} instance for equivalent properties (unless the existing instance was garbage collected
+     *     and thus a new one had to be created), hence these caches will be re-used too. {@link DefaultObjectWrapper} instances
+     *     are cached in the static fields of FreeMarker too, but there's a separate cache for each
+     *     Thread Context Class Loader, which in a servlet container practically means a separate cache for each Web
+     *     Application (each servlet context). (This is like so because for resolving class names to classes FreeMarker
+     *     uses the Thread Context Class Loader, so the result of the resolution can be different for different
+     *     Thread Context Class Loaders.) The model caches are:
+     *     <ul>
+     *       <li><p>
+     *         Static model caches: These are used by the hash returned by {@link DefaultObjectWrapper#getEnumModels()} and
+     *         {@link DefaultObjectWrapper#getStaticModels()}, for caching {@link TemplateModel}-s for the static methods/fields
+     *         and Java enums that were accessed through them. To use said hashes, you have to put them
+     *         explicitly into the data-model or expose them to the template explicitly otherwise, so in most applications
+     *         these caches aren't unused.
+     *       </li>
+     *       <li><p>
+     *         Instance model cache: By default off (see {@link ExtendableBuilder#setUseModelCache(boolean)}). Caches the
+     *         {@link TemplateModel}-s for all Java objects that were accessed from templates.
+     *       </li>
+     *     </ul>
+     *   </li>
+     * </ul>
+     *
+     * <p>Note that what this method documentation says about {@link DefaultObjectWrapper} also applies to
+     * {@link Builder}.
+     */
+    public static final class Builder extends ExtendableBuilder<DefaultObjectWrapper, Builder> {
+
+        private final static Map<ClassLoader, Map<Builder, WeakReference<DefaultObjectWrapper>>>
+                INSTANCE_CACHE = new WeakHashMap<>();
+        private final static ReferenceQueue<DefaultObjectWrapper> INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue<>();
+
+        /**
+         * See {@link ExtendableBuilder#ExtendableBuilder(Version, boolean)}
+         */
+        public Builder(Version incompatibleImprovements) {
+            super(incompatibleImprovements, false);
+        }
+
+        /** For unit testing only */
+        static void clearInstanceCache() {
+            synchronized (INSTANCE_CACHE) {
+                INSTANCE_CACHE.clear();
+            }
+        }
+
+        /**
+         * Returns a {@link DefaultObjectWrapper} instance that matches the settings of this builder. This will be possibly
+         * a singleton that is also in use elsewhere.
+         */
+        public DefaultObjectWrapper build() {
+            return _ModelAPI.getDefaultObjectWrapperSubclassSingleton(
+                    this, INSTANCE_CACHE, INSTANCE_CACHE_REF_QUEUE, ConstructorInvoker.INSTANCE);
+        }
+
+        /**
+         * For unit testing only
+         */
+        static Map<ClassLoader, Map<Builder, WeakReference<DefaultObjectWrapper>>> getInstanceCache() {
+            return INSTANCE_CACHE;
+        }
+
+        private static class ConstructorInvoker
+            implements _ModelAPI._ConstructorInvoker<DefaultObjectWrapper, Builder> {
+
+            private static final ConstructorInvoker INSTANCE = new ConstructorInvoker();
+
+            @Override
+            public DefaultObjectWrapper invoke(Builder builder) {
+                return new DefaultObjectWrapper(builder, true);
+            }
+        }
+
+    }
+
+    /**
+     * Holds {@link DefaultObjectWrapper} configuration settings and defines their defaults.
+     * You will not use this abstract class directly, but concrete subclasses like {@link Builder}.
+     * Unless, you are developing a builder for a custom {@link DefaultObjectWrapper} subclass. In that case, note that
+     * overriding the {@link #equals} and {@link #hashCode} is important, as these objects are used as {@link ObjectWrapper}
+     * singleton lookup keys.
+     */
+    protected abstract static class ExtendableBuilder<
+            ProductT extends DefaultObjectWrapper, SelfT extends ExtendableBuilder<ProductT, SelfT>>
+            extends BuilderBase<ProductT, SelfT> implements Cloneable {
+
+        private final Version incompatibleImprovements;
+
+        ClassIntrospector.Builder classIntrospectorBuilder;
+
+        // Properties and their *defaults*:
+        private boolean simpleMapWrapper;
+        private int defaultDateType = TemplateDateModel.UNKNOWN;
+        private ObjectWrapper outerIdentity;
+        private boolean strict;
+        private boolean useModelCache;
+        private boolean usePrivateCaches;
+        // Attention!
+        // - As this object is a cache key, non-normalized field values should be avoided.
+        // - Fields with default values must be set until the end of the constructor to ensure that when the lookup happens,
+        //   there will be no unset fields.
+        // - If you add a new field, review all methods in this class
+
+        /**
+         * @param incompatibleImprovements
+         *   Sets which of the non-backward-compatible improvements should be enabled. Not {@code null}. This version number
+         *   is the same as the FreeMarker version number with which the improvements were implemented.
+         *
+         *   <p>For new projects, it's recommended to set this to the FreeMarker version that's used during the development.
+         *   For released products that are still actively developed it's a low risk change to increase the 3rd
+         *   version number further as FreeMarker is updated, but of course you should always check the list of effects
+         *   below. Increasing the 2nd or 1st version number possibly mean substantial changes with higher risk of breaking
+         *   the application, but again, see the list of effects below.
+         *
+         *   <p>The reason it's separate from {@link Configuration#setIncompatibleImprovements(Version)} is that
+         *   {@link ObjectWrapper} objects are often shared among multiple {@link Configuration}-s, so the two version
+         *   numbers are technically independent. But it's recommended to keep those two version numbers the same.
+         *
+         *   <p>The changes enabled by {@code incompatibleImprovements} are:
+         *   <ul>
+         *     <li>
+         *       <p>3.0.0: No changes; this is the starting point, the version used in older projects.
+         *     </li>
+         *   </ul>
+         *
+         *   <p>Note that the version will be normalized to the lowest version where the same incompatible
+         *   {@link DefaultObjectWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might returns
+         *   a lower version than what you have specified.
+         * @param isIncompImprsAlreadyNormalized
+         *         Tells if the {@code incompatibleImprovements} parameter contains an <em>already normalized</em> value.
+         *         This parameter meant to be {@code true} when the class that extends {@link DefaultObjectWrapper} needs to
+         *         add additional breaking versions over those of {@link DefaultObjectWrapper}. Thus, if this parameter is
+         *         {@code true}, the versions where {@link DefaultObjectWrapper} had breaking changes must be already
+         *         factored into the {@code incompatibleImprovements} parameter value, as no more normalization will happen.
+         *         (You can use {@link DefaultObjectWrapper#normalizeIncompatibleImprovementsVersion(Version)} to discover
+         *         those.)
+         */
+        protected ExtendableBuilder(Version incompatibleImprovements, boolean isIncompImprsAlreadyNormalized) {
+            _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
+
+            incompatibleImprovements = isIncompImprsAlreadyNormalized
+                    ? incompatibleImprovements
+                    : normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
+            this.incompatibleImprovements = incompatibleImprovements;
+
+            classIntrospectorBuilder = new ClassIntrospector.Builder(incompatibleImprovements);
+        }
+
+        /**
+         * Properly implementing this method is important if the builder is used as a cache key; if you override
+         * {@link ExtendableBuilder} and add new fields, don't forget to override it!
+         */
+        // TODO Move this to Builder and a static helper method
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + incompatibleImprovements.hashCode();
+            result = prime * result + (simpleMapWrapper ? 1231 : 1237);
+            result = prime * result + defaultDateType;
+            result = prime * result + (outerIdentity != null ? outerIdentity.hashCode() : 0);
+            result = prime * result + (strict ? 1231 : 1237);
+            result = prime * result + (useModelCache ? 1231 : 1237);
+            result = prime * result + (usePrivateCaches ? 1231 : 1237);
+            result = prime * result + classIntrospectorBuilder.hashCode();
+            return result;
+        }
+
+        /**
+         * Two {@link ExtendableBuilder}-s are equal exactly if their classes are identical ({@code ==}), and their
+         * field values are equal. Properly implementing this method is important if the builder is used as a cache key;
+         * if you override {@link ExtendableBuilder} and add new fields, don't forget to override it!
+         */
+        // TODO Move this to Builder and a static helper method
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (obj == null) return false;
+            if (getClass() != obj.getClass()) return false;
+            ExtendableBuilder other = (ExtendableBuilder) obj;
+
+            if (!incompatibleImprovements.equals(other.incompatibleImprovements)) return false;
+            if (simpleMapWrapper != other.simpleMapWrapper) return false;
+            if (defaultDateType != other.defaultDateType) return false;
+            if (outerIdentity != other.outerIdentity) return false;
+            if (strict != other.strict) return false;
+            if (useModelCache != other.useModelCache) return false;
+            if (usePrivateCaches != other.usePrivateCaches) return false;
+            return classIntrospectorBuilder.equals(other.classIntrospectorBuilder);
+        }
+
+        /**
+         * In case the builder is used as a cache key, this is used to clone it before it's actually used as a key; if
+         * you override {@link ExtendableBuilder} and add new fields that needs deep cloning, don't forget to
+         * override it! Calls {@link Object#clone()} internally (among others), so newly added fields are automatically
+         * copied, but again, that's not enough if the field value is mutable.
+         */
+        // TODO Move this to Builder and DeepCloneableBuilder
+        protected SelfT deepClone() {
+            try {
+                @SuppressWarnings("unchecked") SelfT clone = (SelfT) super.clone();
+                clone.classIntrospectorBuilder = (ClassIntrospector.Builder) classIntrospectorBuilder.clone();
+                return clone;
+            } catch (CloneNotSupportedException e) {
+                throw new RuntimeException("Failed to deepClone ExtendableBuilder", e);
+            }
+        }
+
+        public Version getIncompatibleImprovements() {
+            return incompatibleImprovements;
+        }
+
+        /**
+         * Getter pair of {@link #setDefaultDateType(int)}
+         */
+        public int getDefaultDateType() {
+            return defaultDateType;
+        }
+
+        /**
+         * Sets the default date type to use for date models that result from
+         * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
+         * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is
+         * {@link TemplateDateModel#UNKNOWN}.
+         * @param defaultDateType the new default date type.
+         */
+        public void setDefaultDateType(int defaultDateType) {
+            this.defaultDateType = defaultDateType;
+        }
+
+        /**
+         * Fluent API equivalent of {@link #setDefaultDateType(int)}.
+         */
+        public SelfT defaultDateType(int defaultDateType) {
+            setDefaultDateType(defaultDateType);
+            return self();
+        }
+
+        /**
+         * Getter pair of {@link #setOuterIdentity(ObjectWrapper)}.
+         */
+        public ObjectWrapper getOuterIdentity() {
+            return outerIdentity;
+        }
+
+        /**
+         * When wrapping an object, the DefaultObjectWrapper commonly needs to wrap "sub-objects", for example each
+         * element in a wrapped collection. Normally it wraps these objects using itself. However, this makes it
+         * difficult to delegate to a DefaultObjectWrapper as part of a custom aggregate ObjectWrapper. This method lets
+         * you set the ObjectWrapper which will be used to wrap the sub-objects.
+         *
+         * @param outerIdentity
+         *         the aggregate ObjectWrapper, or {@code null} if we will use the object created by this builder.
+         */
+        public void setOuterIdentity(ObjectWrapper outerIdentity) {
+            this.outerIdentity = outerIdentity;
+        }
+
+        /**
+         * Fluent API equivalent of {@link #setOuterIdentity(ObjectWrapper)}.
+         */
+        public SelfT outerIdentity(ObjectWrapper outerIdentity) {
+            setOuterIdentity(outerIdentity);
+            return self();
+        }
+
+        /**
+         * Getter pair of {@link #setStrict(boolean)}.
+         */
+        public boolean isStrict() {
+            return strict;
+        }
+
+        /**
+         * Specifies if an attempt to read a bean property that doesn't exist in the
+         * wrapped object should throw an {@link InvalidPropertyException}.
+         *
+         * <p>If this property is <tt>false</tt> (the default) then an attempt to read
+         * a missing bean property is the same as reading an existing bean property whose
+         * value is <tt>null</tt>. The template can't tell the difference, and thus always
+         * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins
+         * to handle the situation.
+         *
+         * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in
+         * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean
+         * object (as opposed to just holding <tt>null</tt> value) will cause
+         * {@link InvalidPropertyException}, which can't be suppressed in the template
+         * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way
+         * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to
+         * handle existing properties whose value is <tt>null</tt>, without the risk of
+         * hiding typos in the property names. Typos will always cause error. But mind you, it
+         * goes against the basic approach of FreeMarker, so use this feature only if you really
+         * know what you are doing.
+         */
+        public void setStrict(boolean strict) {
+            this.strict = strict;
+        }
+
+        /**
+         * Fluent API equivalent of {@link #setStrict(boolean)}.
+         */
+        public SelfT strict(boolean strict) {
+            setStrict(strict);
+            return self();
+        }
+
+        public boolean getUseModelCache() {
+            return useModelCache;
+        }
+
+        /**
+         * @deprecated Does nothing in FreeMarker 3 - we kept it for now to postopne reworking some JUnit tests.
+         */
+        // [FM3] Remove
+        public void setUseModelCache(boolean useModelCache) {
+            this.useModelCache = useModelCache;
+        }
+
+        /**
+         * Fluent API equivalent of {@link #setUseModelCache(boolean)}.
+         * @deprecated Does nothing in FreeMarker 3 - we kept it for now to postopne reworking some JUnit tests.
+         */
+        public SelfT useModelCache(boolean useModelCache) {
+            setUseModelCache(useModelCache);
+            return self();
+        }
+
+        /**
+         * Getter pair of {@link #setUsePrivateCaches(boolean)}.
+         */
+        public boolean getUsePrivateCaches() {
+            return usePrivateCaches;
+        }
+
+        /**
+         * Tells if the instance cerates should try to caches with other {@link DefaultObjectWrapper} instances (where
+         * possible), or it should always invoke its own caches and not share that with anyone else.
+         * */
+        public void setUsePrivateCaches(boolean usePrivateCaches) {
+            this.usePrivateCaches = usePrivateCaches;
+        }
+
+        /**
+         * Fluent API equivalent of {@link #setUsePrivateCaches(boolean)}
+         */
+        public SelfT usePrivateCaches(boolean usePrivateCaches) {
+            setUsePrivateCaches(usePrivateCaches);
+            return self();
+        }
+
+        public int getExposureLevel() {
+            return classIntrospectorBuilder.getExposureLevel();
+        }
+
+        /**
+         * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
+         * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
+         * constants.
+         */
+        public void setExposureLevel(int exposureLevel) {
+            classIntrospectorBuilder.setExposureLevel(exposureLevel);
+        }
+
+        public SelfT exposureLevel(int exposureLevel) {
+            setExposureLevel(exposureLevel);
+            return self();
+        }
+
+        /**
+         * Getter pair of {@link #setExposeFields(boolean)}
+         */
+        public boolean getExposeFields() {
+            return classIntrospectorBuilder.getExposeFields();
+        }
+
+        /**
+         * Controls whether public instance fields of classes are exposed to
+         * templates.
+         * @param exposeFields if set to true, public instance fields of classes
+         * that do not have a property getter defined can be accessed directly by
+         * their name. If there is a property getter for a property of the same
+         * name as the field (i.e. getter "getFoo()" and field "foo"), then
+         * referring to "foo" in template invokes the getter. If set to false, no
+         * access to public instance fields of classes is given. Default is false.
+         */
+        public void setExposeFields(boolean exposeFields) {
+            classIntrospectorBuilder.setExposeFields(exposeFields);
+        }
+
+        /**
+         * Fluent API equivalent of {@link #setExposeFields(boolean)}
+         */
+        public SelfT exposeFields(boolean exposeFields) {
+            setExposeFields(exposeFields);
+            return self();
+        }
+
+        /**
+         * Getter pair of {@link #setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}
+         */
+        public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
+            return classIntrospectorBuilder.getMethodAppearanceFineTuner();
+        }
+
+        /**
+         * Used to tweak certain aspects of how methods appear in the data-model;
+         * see {@link MethodAppearanceFineTuner} for more.
+         * Setting this to non-{@code null} will disable class introspection cache sharing, unless
+         * the value implements {@link SingletonCustomizer}.
+         */
+        public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) {
+            classIntrospectorBuilder.setMethodAppearanceFineTuner(methodAppearanceFineTuner);
+        }
+
+        /**
+         * Fluent API equivalent of {@link #setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}
+         */
+        public SelfT methodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) {
+            setMethodAppearanceFineTuner(methodAppearanceFineTuner);
+            return self();
+        }
+
+        /**
+         * Used internally for testing.
+         */
+        MethodSorter getMethodSorter() {
+            return classIntrospectorBuilder.getMethodSorter();
+        }
+
+        /**
+         * Used internally for testing.
+         */
+        void setMethodSorter(MethodSorter methodSorter) {
+            classIntrospectorBuilder.setMethodSorter(methodSorter);
+        }
+
+        /**
+         * Used internally for testing.
+         */
+        SelfT methodSorter(MethodSorter methodSorter) {
+            setMethodSorter(methodSorter);
+            return self();
+        }
+
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperBuilder.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperBuilder.java
deleted file mode 100644
index c8d1f4f..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperBuilder.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.model.TemplateModel;
-
-/**
- * Gets/creates a {@link DefaultObjectWrapper} singleton instance that's already configured as specified in the properties of
- * this object; this is recommended over using the {@link DefaultObjectWrapper} constructors. The returned instance can't be
- * further configured (it's write protected).
- *
- * <p>The builder meant to be used as a drop-away object (not stored in a field), like in this example:
- * <pre>
- *    DefaultObjectWrapper dow = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_21).build();
- * </pre>
- *
- * <p>Or, a more complex example:</p>
- * <pre>
- *    // Create the builder:
- *    DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_21);
- *    // Set desired DefaultObjectWrapper configuration properties:
- *    builder.setUseModelCache(true);
- *    builder.setExposeFields(true);
- *
- *    // Get the singleton:
- *    DefaultObjectWrapper dow = builder.build();
- *    // You don't need the builder anymore.
- * </pre>
- *
- * <p>Despite that builders aren't meant to be used as long-lived objects (singletons), the builder is thread-safe after
- * you have stopped calling its setters and it was safely published (see JSR 133) to other threads. This can be useful
- * if you have to put the builder into an IoC container, rather than the singleton it produces.
- *
- * <p>The main benefit of using a builder instead of a {@link DefaultObjectWrapper} constructor is that this way the
- * internal object wrapping-related caches (most notably the class introspection cache) will come from a global,
- * JVM-level (more precisely, {@code freemarker-core.jar}-class-loader-level) cache. Also the
- * {@link DefaultObjectWrapper} singletons
- * themselves are stored in this global cache. Some of the wrapping-related caches are expensive to build and can take
- * significant amount of memory. Using builders, components that use FreeMarker will share {@link DefaultObjectWrapper}
- * instances and said caches even if they use separate FreeMarker {@link org.apache.freemarker.core.Configuration}-s. (Many Java libraries use
- * FreeMarker internally, so {@link org.apache.freemarker.core.Configuration} sharing is not an option.)
- *
- * <p>Note that the returned {@link DefaultObjectWrapper} instances are only weak-referenced from inside the builder mechanism,
- * so singletons are garbage collected when they go out of usage, just like non-singletons.
- *
- * <p>About the object wrapping-related caches:
- * <ul>
- *   <li><p>Class introspection cache: Stores information about classes that once had to be wrapped. The cache is
- *     stored in the static fields of certain FreeMarker classes. Thus, if you have two {@link DefaultObjectWrapper}
- *     instances, they might share the same class introspection cache. But if you have two
- *     {@code freemarker.jar}-s (typically, in two Web Application's {@code WEB-INF/lib} directories), those won't
- *     share their caches (as they don't share the same FreeMarker classes).
- *     Also, currently there's a separate cache for each permutation of the property values that influence class
- *     introspection: {@link DefaultObjectWrapperBuilder#setExposeFields(boolean) expose_fields} and
- *     {@link DefaultObjectWrapperBuilder#setExposureLevel(int) exposure_level}. So only {@link DefaultObjectWrapper} where those
- *     properties are the same may share class introspection caches among each other.
- *   </li>
- *   <li><p>Model caches: These are local to a {@link DefaultObjectWrapper}. {@link DefaultObjectWrapperBuilder} returns the same
- *     {@link DefaultObjectWrapper} instance for equivalent properties (unless the existing instance was garbage collected
- *     and thus a new one had to be created), hence these caches will be re-used too. {@link DefaultObjectWrapper} instances
- *     are cached in the static fields of FreeMarker too, but there's a separate cache for each
- *     Thread Context Class Loader, which in a servlet container practically means a separate cache for each Web
- *     Application (each servlet context). (This is like so because for resolving class names to classes FreeMarker
- *     uses the Thread Context Class Loader, so the result of the resolution can be different for different
- *     Thread Context Class Loaders.) The model caches are:
- *     <ul>
- *       <li><p>
- *         Static model caches: These are used by the hash returned by {@link DefaultObjectWrapper#getEnumModels()} and
- *         {@link DefaultObjectWrapper#getStaticModels()}, for caching {@link TemplateModel}-s for the static methods/fields
- *         and Java enums that were accessed through them. To use said hashes, you have to put them
- *         explicitly into the data-model or expose them to the template explicitly otherwise, so in most applications
- *         these caches aren't unused.
- *       </li>
- *       <li><p>
- *         Instance model cache: By default off (see {@link DefaultObjectWrapper#setUseModelCache(boolean)}). Caches the
- *         {@link TemplateModel}-s for all Java objects that were accessed from templates.
- *       </li>
- *     </ul>
- *   </li>
- * </ul>
- *
- * <p>Note that what this method documentation says about {@link DefaultObjectWrapper} also applies to
- * {@link DefaultObjectWrapperBuilder}.
- */
-public class DefaultObjectWrapperBuilder extends DefaultObjectWrapperConfiguration {
-
-    private final static Map<ClassLoader, Map<DefaultObjectWrapperConfiguration, WeakReference<DefaultObjectWrapper>>>
-            INSTANCE_CACHE = new WeakHashMap<>();
-    private final static ReferenceQueue<DefaultObjectWrapper> INSTANCE_CACHE_REF_QUEUE
-            = new ReferenceQueue<>();
-    
-    /**
-     * Creates a builder that creates a {@link DefaultObjectWrapper} with the given {@code incompatibleImprovements};
-     * using at least 2.3.22 is highly recommended. See {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)} for
-     * more information about the impact of {@code incompatibleImprovements} values.
-     */
-    public DefaultObjectWrapperBuilder(Version incompatibleImprovements) {
-        super(incompatibleImprovements);
-    }
-
-    /** For unit testing only */
-    static void clearInstanceCache() {
-        synchronized (INSTANCE_CACHE) {
-            INSTANCE_CACHE.clear();
-        }
-    }
-    
-    /**
-     * Returns a {@link DefaultObjectWrapper} instance that matches the settings of this builder. This will be possibly
-     * a singleton that is also in use elsewhere. 
-     */
-    public DefaultObjectWrapper build() {
-        return _ModelAPI.getDefaultObjectWrapperSubclassSingleton(
-                this, INSTANCE_CACHE, INSTANCE_CACHE_REF_QUEUE, DefaultObjectWrapperFactory.INSTANCE);
-    }
-
-    /**
-     * For unit testing only
-     */
-    static Map<ClassLoader, Map<DefaultObjectWrapperConfiguration, WeakReference<DefaultObjectWrapper>>>
-            getInstanceCache() {
-        return INSTANCE_CACHE;
-    }
-
-    private static class DefaultObjectWrapperFactory
-        implements _ModelAPI._DefaultObjectWrapperSubclassFactory<DefaultObjectWrapper, DefaultObjectWrapperConfiguration> {
-    
-        private static final DefaultObjectWrapperFactory INSTANCE = new DefaultObjectWrapperFactory(); 
-        
-        @Override
-        public DefaultObjectWrapper create(DefaultObjectWrapperConfiguration bwConf) {
-            return new DefaultObjectWrapper(bwConf, true);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java
deleted file mode 100644
index 28111d4..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core._CoreAPI;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateDateModel;
-
-/**
- * Holds {@link DefaultObjectWrapper} configuration settings and defines their defaults.
- * You will not use this abstract class directly, but concrete subclasses like {@link DefaultObjectWrapperBuilder}.
- * Unless, you are developing a builder for a custom {@link DefaultObjectWrapper} subclass. In that case, note that
- * overriding the {@link #equals} and {@link #hashCode} is important, as these objects are used as {@link ObjectWrapper}
- * singleton lookup keys.
- */
-public abstract class DefaultObjectWrapperConfiguration implements Cloneable {
-
-    private final Version incompatibleImprovements;
-
-    ClassIntrospectorBuilder classIntrospectorBuilder;
-
-    // Properties and their *defaults*:
-    private boolean simpleMapWrapper = false;
-    private int defaultDateType = TemplateDateModel.UNKNOWN;
-    private ObjectWrapper outerIdentity = null;
-    private boolean strict = false;
-    private boolean useModelCache = false;
-    // Attention!
-    // - As this object is a cache key, non-normalized field values should be avoided.
-    // - Fields with default values must be set until the end of the constructor to ensure that when the lookup happens,
-    //   there will be no unset fields.
-    // - If you add a new field, review all methods in this class
-
-    /**
-     * @param incompatibleImprovements
-     *            See the corresponding parameter of {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}. Not {@code null}. Note
-     *            that the version will be normalized to the lowest version where the same incompatible
-     *            {@link DefaultObjectWrapper} improvements were already present, so for the returned instance
-     *            {@link #getIncompatibleImprovements()} might returns a lower version than what you have specified
-     *            here.
-     * @param isIncompImprsAlreadyNormalized
-     *            Tells if the {@code incompatibleImprovements} parameter contains an <em>already normalized</em> value.
-     *            This parameter meant to be {@code true} when the class that extends {@link DefaultObjectWrapper} needs to add
-     *            additional breaking versions over those of {@link DefaultObjectWrapper}. Thus, if this parameter is
-     *            {@code true}, the versions where {@link DefaultObjectWrapper} had breaking changes must be already factored
-     *            into the {@code incompatibleImprovements} parameter value, as no more normalization will happen. (You
-     *            can use {@link DefaultObjectWrapper#normalizeIncompatibleImprovementsVersion(Version)} to discover those.)
-     *
-     * @since 2.3.22
-     */
-    protected DefaultObjectWrapperConfiguration(Version incompatibleImprovements, boolean isIncompImprsAlreadyNormalized) {
-        _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
-
-        incompatibleImprovements = isIncompImprsAlreadyNormalized
-                ? incompatibleImprovements
-                : DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
-        this.incompatibleImprovements = incompatibleImprovements;
-
-        classIntrospectorBuilder = new ClassIntrospectorBuilder(incompatibleImprovements);
-    }
-
-    /**
-     * Same as {@link #DefaultObjectWrapperConfiguration(Version, boolean) DefaultObjectWrapperConfiguration(Version, false)}.
-     */
-    protected DefaultObjectWrapperConfiguration(Version incompatibleImprovements) {
-        this(incompatibleImprovements, false);
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + incompatibleImprovements.hashCode();
-        result = prime * result + (simpleMapWrapper ? 1231 : 1237);
-        result = prime * result + defaultDateType;
-        result = prime * result + (outerIdentity != null ? outerIdentity.hashCode() : 0);
-        result = prime * result + (strict ? 1231 : 1237);
-        result = prime * result + (useModelCache ? 1231 : 1237);
-        result = prime * result + classIntrospectorBuilder.hashCode();
-        return result;
-    }
-
-    /**
-     * Two {@link DefaultObjectWrapperConfiguration}-s are equal exactly if their classes are identical ({@code ==}), and their
-     * field values are equal.
-     */
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) return true;
-        if (obj == null) return false;
-        if (getClass() != obj.getClass()) return false;
-        DefaultObjectWrapperConfiguration other = (DefaultObjectWrapperConfiguration) obj;
-
-        if (!incompatibleImprovements.equals(other.incompatibleImprovements)) return false;
-        if (simpleMapWrapper != other.simpleMapWrapper) return false;
-        if (defaultDateType != other.defaultDateType) return false;
-        if (outerIdentity != other.outerIdentity) return false;
-        if (strict != other.strict) return false;
-        if (useModelCache != other.useModelCache) return false;
-        return classIntrospectorBuilder.equals(other.classIntrospectorBuilder);
-    }
-
-    protected Object clone(boolean deepCloneKey) {
-        try {
-            DefaultObjectWrapperConfiguration clone = (DefaultObjectWrapperConfiguration) super.clone();
-            if (deepCloneKey) {
-                clone.classIntrospectorBuilder
-                        = (ClassIntrospectorBuilder) classIntrospectorBuilder.clone();
-            }
-            return clone;
-        } catch (CloneNotSupportedException e) {
-            throw new RuntimeException("Failed to clone DefaultObjectWrapperConfiguration", e);
-        }
-    }
-
-    public int getDefaultDateType() {
-        return defaultDateType;
-    }
-
-    /** See {@link DefaultObjectWrapper#setDefaultDateType(int)}. */
-    public void setDefaultDateType(int defaultDateType) {
-        this.defaultDateType = defaultDateType;
-    }
-
-    public ObjectWrapper getOuterIdentity() {
-        return outerIdentity;
-    }
-
-    /**
-     * See {@link DefaultObjectWrapper#setOuterIdentity(ObjectWrapper)}, except here the default is {@code null} that means
-     * the {@link ObjectWrapper} that you will set up with this {@link DefaultObjectWrapperBuilder} object.
-     */
-    public void setOuterIdentity(ObjectWrapper outerIdentity) {
-        this.outerIdentity = outerIdentity;
-    }
-
-    public boolean isStrict() {
-        return strict;
-    }
-
-    /** See {@link DefaultObjectWrapper#setStrict(boolean)}. */
-    public void setStrict(boolean strict) {
-        this.strict = strict;
-    }
-
-    public boolean getUseModelCache() {
-        return useModelCache;
-    }
-
-    /** See {@link DefaultObjectWrapper#setUseModelCache(boolean)} (it means the same). */
-    public void setUseModelCache(boolean useModelCache) {
-        this.useModelCache = useModelCache;
-    }
-
-    public Version getIncompatibleImprovements() {
-        return incompatibleImprovements;
-    }
-
-    public int getExposureLevel() {
-        return classIntrospectorBuilder.getExposureLevel();
-    }
-
-    /** See {@link DefaultObjectWrapper#setExposureLevel(int)}. */
-    public void setExposureLevel(int exposureLevel) {
-        classIntrospectorBuilder.setExposureLevel(exposureLevel);
-    }
-
-    public boolean getExposeFields() {
-        return classIntrospectorBuilder.getExposeFields();
-    }
-
-    /** See {@link DefaultObjectWrapper#setExposeFields(boolean)}. */
-    public void setExposeFields(boolean exposeFields) {
-        classIntrospectorBuilder.setExposeFields(exposeFields);
-    }
-
-    public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
-        return classIntrospectorBuilder.getMethodAppearanceFineTuner();
-    }
-
-    /**
-     * See {@link DefaultObjectWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}; additionally,
-     * note that currently setting this to non-{@code null} will disable class introspection cache sharing, unless
-     * the value implements {@link SingletonCustomizer}.
-     */
-    public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) {
-        classIntrospectorBuilder.setMethodAppearanceFineTuner(methodAppearanceFineTuner);
-    }
-
-    MethodSorter getMethodSorter() {
-        return classIntrospectorBuilder.getMethodSorter();
-    }
-
-    void setMethodSorter(MethodSorter methodSorter) {
-        classIntrospectorBuilder.setMethodSorter(methodSorter);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java b/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
index 773e8f3..9218bdf 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
@@ -24,7 +24,7 @@ import java.util.List;
 
 /**
  * Used for JUnit testing method-order dependence bugs via
- * {@link DefaultObjectWrapper#setMethodSorter(MethodSorter)}.
+ * {@link DefaultObjectWrapper.Builder#setMethodSorter(MethodSorter)}.
  */
 interface MethodSorter {
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
index 52a7744..e783af8 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
@@ -134,7 +134,7 @@ abstract class OverloadedMethodsSubset {
         MaybeEmptyCallableMemberDescriptor memberDesc
                 = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes);
         if (memberDesc == null) {
-            // Synchronized so that we won't unnecessarily create the same member desc. for multiple times in parallel.
+            // Synchronized so that we won't unnecessarily invoke the same member desc. for multiple times in parallel.
             synchronized (argTypesToMemberDescCache) {
                 memberDesc = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes);
                 if (memberDesc == null) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
index f858cc8..6547923 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
@@ -63,7 +63,7 @@ class OverloadedVarArgsMethods extends OverloadedMethodsSubset {
         // - m(t1), because a varargs array can be 0 long
         //
         // But we can't do that for real, because we had to add infinite number of methods. Also, for efficiency we
-        // don't want to create unwrappingHintsByParamCount entries at the indices which are still unused.
+        // don't want to invoke unwrappingHintsByParamCount entries at the indices which are still unused.
         // So we only update the already existing hints. Remember that we already have m(t1, t2) there.
         
         final int paramCount = paramTypes.length;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java
index 2e738a0..eeec18f 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java
@@ -31,13 +31,8 @@ import org.apache.freemarker.core.model.TemplateModelException;
  */
 public class RestrictedObjectWrapper extends DefaultObjectWrapper {
 
-    /**
-     * @param incompatibleImprovements see in {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}.
-     * 
-     * @since 2.3.21
-     */
-    public RestrictedObjectWrapper(Version incompatibleImprovements) {
-        super(incompatibleImprovements);
+    protected RestrictedObjectWrapper(Builder builder) {
+        super(builder, true);
     }
 
     /**
@@ -55,4 +50,26 @@ public class RestrictedObjectWrapper extends DefaultObjectWrapper {
         throw new TemplateModelException("RestrictedObjectWrapper deliberately doesn't allow ?api.");
     }
 
+    protected static abstract class ExtendableBuilder<
+            ProductT extends RestrictedObjectWrapper, SelfT extends ExtendableBuilder<ProductT,
+            SelfT>> extends DefaultObjectWrapper.ExtendableBuilder<ProductT, SelfT> {
+
+        protected ExtendableBuilder(Version incompatibleImprovements, boolean isIncompImprsAlreadyNormalized) {
+            super(incompatibleImprovements, isIncompImprsAlreadyNormalized);
+        }
+
+    }
+
+    public static final class Builder extends ExtendableBuilder<RestrictedObjectWrapper, Builder> {
+
+        public Builder(Version incompatibleImprovements) {
+            super(incompatibleImprovements, false);
+        }
+
+        @Override
+        public RestrictedObjectWrapper build() {
+            return new RestrictedObjectWrapper(this);
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
index 16ec685..ef475ca 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
@@ -65,7 +65,7 @@ import org.apache.freemarker.core.model.WrappingTemplateModel;
  * to which it had to be passed adapted to a {@link Map}).
  * 
  * <p>
- * If regardless of which of the above two cases stand, you just need to (or more convenient to) create the hash from a
+ * If regardless of which of the above two cases stand, you just need to (or more convenient to) invoke the hash from a
  * {@link Map} (via {@link SimpleHash#SimpleHash(Map, ObjectWrapper)} or
  * {@link SimpleHash#SimpleHash(Map, ObjectWrapper)}), which will be the faster depends on how many times will the
  * <em>same</em> {@link Map} entry be read from the template(s) later, on average. If, on average, you read each entry

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
index 17cd03a..8811180 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
@@ -59,7 +59,7 @@ import org.apache.freemarker.core.model.WrappingTemplateModel;
  * from a plain Java method to which it had to be passed adapted to a {@link List}).
  * 
  * <p>
- * If regardless of which of the above two cases stand, you just need to (or more convenient to) create the sequence
+ * If regardless of which of the above two cases stand, you just need to (or more convenient to) invoke the sequence
  * from a {@link List} (via {@link DefaultListAdapter#adapt(List, RichObjectWrapper)} or
  * {@link SimpleSequence#SimpleSequence(Collection)}), which will be the faster depends on how many times will the
  * <em>same</em> {@link List} entry be read from the template(s) later, on average. If, on average, you read each entry

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java b/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java
index 554d711..e4a0e5a 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java
@@ -25,7 +25,7 @@ import org.apache.freemarker.core.model.ObjectWrapper;
  * Marker interface useful when used together with {@link MethodAppearanceFineTuner} and such customizer objects, to
  * indicate that it <b>doesn't contain reference to the {@link ObjectWrapper}</b> (so beware with non-static inner
  * classes) and can be and should be used in call introspection cache keys. This also implies that you won't
- * create many instances of the class, rather just reuse the same (or same few) instances over and over. Furthermore,
+ * invoke many instances of the class, rather just reuse the same (or same few) instances over and over. Furthermore,
  * the instances must be thread-safe. The typical pattern in which this instance should be used is like this:
  * 
  * <pre>static class MyMethodAppearanceFineTuner implements MethodAppearanceFineTuner, SingletonCustomizer {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java b/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java
index e5c9624..6e09e9b 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java
@@ -32,7 +32,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util.BuilderBase;
 import org.apache.freemarker.core.util._CollectionUtil;
 
 /**
@@ -129,24 +129,26 @@ public class _ModelAPI {
     /**
      * Contains the common parts of the singleton management for {@link DefaultObjectWrapper} and {@link DefaultObjectWrapper}.  
      *  
-     * @param dowSubclassFactory Creates a <em>new</em> read-only object wrapper of the desired
+     * @param dowConstructorInvoker Creates a <em>new</em> read-only object wrapper of the desired
      *     {@link DefaultObjectWrapper} subclass. 
      */
-    // [FM3] Unnecessary generalization, unless we publish this API
-    public static <OW extends DefaultObjectWrapper, OWC extends DefaultObjectWrapperConfiguration> OW
-    getDefaultObjectWrapperSubclassSingleton(
-            OWC settings,
-            Map<ClassLoader, Map<OWC, WeakReference<OW>>> instanceCache,
-            ReferenceQueue<OW> instanceCacheRefQue,
-            _DefaultObjectWrapperSubclassFactory<OW, OWC> dowSubclassFactory) {
+    // [FM3] Generalize and publish this functionality
+    public static <
+            ObjectWrapperT extends DefaultObjectWrapper,
+            BuilderT extends DefaultObjectWrapper.ExtendableBuilder<ObjectWrapperT, BuilderT>>
+    ObjectWrapperT getDefaultObjectWrapperSubclassSingleton(
+            BuilderT builder,
+            Map<ClassLoader, Map<BuilderT, WeakReference<ObjectWrapperT>>> instanceCache,
+            ReferenceQueue<ObjectWrapperT> instanceCacheRefQue,
+            _ConstructorInvoker<ObjectWrapperT, BuilderT> dowConstructorInvoker) {
         // DefaultObjectWrapper can't be cached across different Thread Context Class Loaders (TCCL), because the result of
         // a class name (String) to Class mappings depends on it, and the staticModels and enumModels need that.
         // (The ClassIntrospector doesn't have to consider the TCCL, as it only works with Class-es, not class
         // names.)
         ClassLoader tccl = Thread.currentThread().getContextClassLoader();
         
-        Reference<OW> instanceRef;
-        Map<OWC, WeakReference<OW>> tcclScopedCache;
+        Reference<ObjectWrapperT> instanceRef;
+        Map<BuilderT, WeakReference<ObjectWrapperT>> tcclScopedCache;
         synchronized (instanceCache) {
             tcclScopedCache = instanceCache.get(tccl);
             if (tcclScopedCache == null) {
@@ -154,27 +156,24 @@ public class _ModelAPI {
                 instanceCache.put(tccl, tcclScopedCache);
                 instanceRef = null;
             } else {
-                instanceRef = tcclScopedCache.get(settings);
+                instanceRef = tcclScopedCache.get(builder);
             }
         }
 
-        OW instance = instanceRef != null ? instanceRef.get() : null;
+        ObjectWrapperT instance = instanceRef != null ? instanceRef.get() : null;
         if (instance != null) {  // cache hit
             return instance;
         }
         // cache miss
         
-        settings = clone(settings);  // prevent any aliasing issues 
-        instance = dowSubclassFactory.create(settings);
-        if (!instance.isWriteProtected()) {
-            throw new BugException();
-        }
-        
+        builder = builder.deepClone();  // prevent any aliasing issues
+        instance = dowConstructorInvoker.invoke(builder);
+
         synchronized (instanceCache) {
-            instanceRef = tcclScopedCache.get(settings);
-            OW concurrentInstance = instanceRef != null ? instanceRef.get() : null;
+            instanceRef = tcclScopedCache.get(builder);
+            ObjectWrapperT concurrentInstance = instanceRef != null ? instanceRef.get() : null;
             if (concurrentInstance == null) {
-                tcclScopedCache.put(settings, new WeakReference<>(instance, instanceCacheRefQue));
+                tcclScopedCache.put(builder, new WeakReference<>(instance, instanceCacheRefQue));
             } else {
                 instance = concurrentInstance;
             }
@@ -185,20 +184,16 @@ public class _ModelAPI {
         return instance;
     }
 
-    @SuppressWarnings("unchecked")
-    private static <BWC extends DefaultObjectWrapperConfiguration> BWC clone(BWC settings) {
-        return (BWC) settings.clone(true);
-    }
-    
-    private static <BW extends DefaultObjectWrapper, BWC extends DefaultObjectWrapperConfiguration>
+    private static <
+            ObjectWrapperT extends DefaultObjectWrapper, BuilderT extends DefaultObjectWrapper.ExtendableBuilder>
             void removeClearedReferencesFromCache(
-                    Map<ClassLoader, Map<BWC, WeakReference<BW>>> instanceCache,
-                    ReferenceQueue<BW> instanceCacheRefQue) {
-        Reference<? extends BW> clearedRef;
+                    Map<ClassLoader, Map<BuilderT, WeakReference<ObjectWrapperT>>> instanceCache,
+                    ReferenceQueue<ObjectWrapperT> instanceCacheRefQue) {
+        Reference<? extends ObjectWrapperT> clearedRef;
         while ((clearedRef = instanceCacheRefQue.poll()) != null) {
             synchronized (instanceCache) {
-                findClearedRef: for (Map<BWC, WeakReference<BW>> tcclScopedCache : instanceCache.values()) {
-                    for (Iterator<WeakReference<BW>> it2 = tcclScopedCache.values().iterator(); it2.hasNext(); ) {
+                findClearedRef: for (Map<BuilderT, WeakReference<ObjectWrapperT>> tcclScopedCache : instanceCache.values()) {
+                    for (Iterator<WeakReference<ObjectWrapperT>> it2 = tcclScopedCache.values().iterator(); it2.hasNext(); ) {
                         if (it2.next() == clearedRef) {
                             it2.remove();
                             break findClearedRef;
@@ -211,11 +206,12 @@ public class _ModelAPI {
     
     /**
      * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+     * Used when the builder delegates the product creation to something else (typically, an instance cache). Calling
+     * {@link BuilderBase#build()} would be infinite recursion in such cases.
      */
-    public interface _DefaultObjectWrapperSubclassFactory<BW extends DefaultObjectWrapper, BWC extends DefaultObjectWrapperConfiguration> {
+    public interface _ConstructorInvoker<ProductT, BuilderT> {
         
-        /** Creates a new read-only {@link DefaultObjectWrapper}; used for {@link DefaultObjectWrapperBuilder} and such. */
-        BW create(BWC sa);
+        ProductT invoke(BuilderT builder);
     }
     
 }



[4/4] incubator-freemarker git commit: Made DefaultObjectWrapper immtutable (no more setters), also removed public constructors. Instead, instances are created with DefaultObjectWrapper.Builder.build(). Reworked DefaultObjectWrapper and ClassIntrospector

Posted by dd...@apache.org.
Made DefaultObjectWrapper immtutable (no more setters), also removed public constructors. Instead, instances are created with DefaultObjectWrapper.Builder.build().
Reworked DefaultObjectWrapper and ClassIntrospector builder classes to be Xxx.Builder instead of XxxBuilder. Object builder expressions (used in setting values) also recognize this convention now.
Added fluent API setters to DefaultObjectWrapper.Builder.
Added common superclass for builders, which supports fluent API-s. Other DefaultObjectWrapper builder related cleanups.
Along the way, removed DefaultObjectWrapper.methodsShadowItems setting; methods will always shadow things that the "generic get" (that is, get(String) that doesn't come from Map or any known interface) could return.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/dceec32e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/dceec32e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/dceec32e

Branch: refs/heads/3
Commit: dceec32eda765c4d3ef1f46ce1cce93760855d8a
Parents: 529cdd8
Author: ddekany <dd...@apache.org>
Authored: Thu Mar 16 18:12:38 2017 +0100
Committer: ddekany <dd...@apache.org>
Committed: Fri Mar 17 22:12:14 2017 +0100

----------------------------------------------------------------------
 build.xml                                       |   2 +-
 .../core/ASTDirCapturingAssignment.java         |   2 +-
 .../apache/freemarker/core/ASTExpression.java   |   2 +-
 .../apache/freemarker/core/Configurable.java    |   9 +-
 .../apache/freemarker/core/Configuration.java   |  10 +-
 .../apache/freemarker/core/CustomAttribute.java |   2 +-
 .../freemarker/core/DirectiveCallPlace.java     |   2 +-
 .../org/apache/freemarker/core/Environment.java |   6 +-
 .../org/apache/freemarker/core/Template.java    |   2 +-
 .../apache/freemarker/core/TokenMgrError.java   |   2 +-
 .../core/_ObjectBuilderSettingEvaluator.java    |  63 +-
 .../core/_SettingEvaluationEnvironment.java     |   2 +-
 .../core/debug/RmiDebuggedEnvironmentImpl.java  |   3 +-
 .../core/debug/RmiDebuggerListenerImpl.java     |   2 +-
 .../core/model/TemplateMethodModelEx.java       |   2 +-
 .../freemarker/core/model/impl/BeanModel.java   |  28 +-
 .../core/model/impl/ClassIntrospector.java      | 180 +++-
 .../model/impl/ClassIntrospectorBuilder.java    | 190 ----
 .../core/model/impl/DefaultObjectWrapper.java   | 880 +++++++++++--------
 .../model/impl/DefaultObjectWrapperBuilder.java | 159 ----
 .../impl/DefaultObjectWrapperConfiguration.java | 216 -----
 .../core/model/impl/MethodSorter.java           |   2 +-
 .../model/impl/OverloadedMethodsSubset.java     |   2 +-
 .../model/impl/OverloadedVarArgsMethods.java    |   2 +-
 .../model/impl/RestrictedObjectWrapper.java     |  31 +-
 .../freemarker/core/model/impl/SimpleHash.java  |   2 +-
 .../core/model/impl/SimpleSequence.java         |   2 +-
 .../core/model/impl/SingletonCustomizer.java    |   2 +-
 .../freemarker/core/model/impl/_ModelAPI.java   |  68 +-
 .../templateresolver/TemplateLookupContext.java |   2 +-
 ...TemplateLoaderBasedTemplateLookupResult.java |   6 +-
 .../freemarker/core/util/BuilderBase.java       |  34 +
 .../core/util/ProductWrappingBuilder.java       |  39 +
 .../freemarker/core/util/WriteProtectable.java  |  37 -
 .../apache/freemarker/core/util/_DateUtil.java  |   2 +-
 ...AliasTargetTemplateValueFormatException.java |   2 +-
 .../impl/AliasTemplateDateFormatFactory.java    |   2 +-
 .../impl/AliasTemplateNumberFormatFactory.java  |   2 +-
 .../impl/JavaTemplateNumberFormatFactory.java   |   2 +-
 .../freemarker/servlet/FreemarkerServlet.java   |  12 +-
 .../apache/freemarker/servlet/IncludePage.java  |   2 +-
 .../jsp/CustomTagAndELFunctionCombiner.java     |   2 +-
 .../freemarker/servlet/jsp/TaglibFactory.java   |   2 +-
 .../servlet/jsp/_FreeMarkerPageContext21.java   |   2 +-
 src/manual/en_US/FM3-CHANGE-LOG.txt             |   8 +-
 .../org/apache/freemarker/core/ASTPrinter.java  |   2 +-
 .../freemarker/core/ConfigurationTest.java      |   5 +-
 .../freemarker/core/IncludeAndImportTest.java   |   2 +-
 .../freemarker/core/IteratorIssuesTest.java     |   3 +-
 .../apache/freemarker/core/ListErrorsTest.java  |   2 +-
 .../core/ObjectBuilderSettingsTest.java         |  45 +-
 .../core/RestrictedObjectWrapperTest.java       |   4 +-
 .../core/RestrictedObjetWrapperTest.java        | 112 +++
 .../freemarker/core/SimpleObjetWrapperTest.java | 112 ---
 .../core/TagSyntaxVariationsTest.java           |   2 +-
 .../core/TemplateConfigurationTest.java         |   2 +-
 .../impl/AbstractParallelIntrospectionTest.java |   3 +-
 .../model/impl/DefaultObjectWrapperDesc.java    |   5 +-
 .../model/impl/DefaultObjectWrapperInc.java     |   5 +-
 ...jectWrapperModelFactoryRegistrationTest.java |  35 +-
 .../impl/DefaultObjectWrapperReadOnlyTest.java  |  87 --
 .../DefaultObjectWrapperSingletonsTest.java     | 100 +--
 .../model/impl/DefaultObjectWrapperTest.java    |  74 +-
 .../DefaultObjectWrapperWithShortedMethods.java |  41 -
 .../DefaultObjectWrapperWithSortedMethods.java  |  40 -
 .../core/model/impl/EnumModelsTest.java         |   3 +-
 .../core/model/impl/ErrorMessagesTest.java      |   4 +-
 .../impl/FineTuneMethodAppearanceTest.java      |   7 +-
 .../Java7MembersOnlyDefaultObjectWrapper.java   |   5 +-
 ...a8DefaultObjectWrapperBridgeMethodsTest.java |   2 +-
 .../impl/Java8DefaultObjectWrapperTest.java     |   2 +-
 .../model/impl/ModelAPINewInstanceTest.java     |   2 +-
 .../core/model/impl/ModelCacheTest.java         |   6 +-
 .../core/model/impl/StaticModelsTest.java       |   3 +-
 .../core/model/impl/TypeFlagsTest.java          |   2 +-
 .../FileTemplateLoaderTest.java                 |   2 +-
 .../servlet/jsp/RealServletContainertTest.java  |   7 +-
 .../freemarker/servlet/jsp/TLDParsingTest.java  |   3 +-
 .../freemarker/test/ResourcesExtractor.java     |   2 +-
 .../freemarker/test/servlet/WebAppTestCase.java |   2 +-
 .../test/templatesuite/TemplateTestCase.java    |   3 +-
 .../test/templatesuite/models/Listables.java    |   7 +-
 .../test/templatesuite/models/MultiModel1.java  |   4 +-
 .../models/TransformHashWrapper.java            |   4 +-
 .../SimpleMapAndCollectionObjectWrapper.java    |   2 +-
 .../apache/freemarker/test/util/XMLLoader.java  |   2 +-
 86 files changed, 1181 insertions(+), 1605 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/build.xml
----------------------------------------------------------------------
diff --git a/build.xml b/build.xml
index 5127c6d..c4a7471 100644
--- a/build.xml
+++ b/build.xml
@@ -369,7 +369,7 @@
     <delete includeEmptyDirs="yes">
       <fileset dir="build/api" includes="**/*" />
     </delete>
-    <!-- javadoc with <fileset> has bugs, so we create a filtered copy: -->
+    <!-- javadoc with <fileset> has bugs, so we invoke a filtered copy: -->
     <copy todir="build/javadoc-sources">
       <fileset dir="src/main/java">
         <exclude name="**/_*.java" />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java b/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
index 1df39d3..9aa5eab 100644
--- a/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
+++ b/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
@@ -99,7 +99,7 @@ final class ASTDirCapturingAssignment extends ASTDirective {
                         result = capturedStringToModel(toString());
                     } catch (TemplateModelException e) {
                         // [Java 1.6] e to cause
-                        throw new IOException("Failed to create FTL value from captured string: " + e);
+                        throw new IOException("Failed to invoke FTL value from captured string: " + e);
                     }
                     switch(scope) {
                         case ASTDirAssignment.NAMESPACE: {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/ASTExpression.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTExpression.java b/src/main/java/org/apache/freemarker/core/ASTExpression.java
index 01ede1e..be00f66 100644
--- a/src/main/java/org/apache/freemarker/core/ASTExpression.java
+++ b/src/main/java/org/apache/freemarker/core/ASTExpression.java
@@ -163,7 +163,7 @@ abstract class ASTExpression extends ASTNode {
     
     static class ReplacemenetState {
         /**
-         * If the replacement expression is not in use yet, we don't have to clone it.
+         * If the replacement expression is not in use yet, we don't have to deepClone it.
          */
         boolean replacementAlreadyInUse; 
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configurable.java b/src/main/java/org/apache/freemarker/core/Configurable.java
index c2aeb85..b6c73eb 100644
--- a/src/main/java/org/apache/freemarker/core/Configurable.java
+++ b/src/main/java/org/apache/freemarker/core/Configurable.java
@@ -45,7 +45,6 @@ import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
@@ -1173,7 +1172,7 @@ public class Configurable {
 
     /**
      * Sets the object wrapper used to wrap objects to {@link TemplateModel}-s.
-     * The default is {@link DefaultObjectWrapperBuilder#build()}.
+     * The default is {@link DefaultObjectWrapper.Builder#build()}.
      */
     public void setObjectWrapper(ObjectWrapper objectWrapper) {
         _NullArgumentException.check("objectWrapper", objectWrapper);
@@ -1503,7 +1502,7 @@ public class Configurable {
     /**
      * Adds an invisible <code>#import <i>templateName</i> as <i>namespaceVarName</i></code> at the beginning of the
      * main template (that's the top-level template that wasn't included/imported from another template). While it only
-     * affects the main template directly, as the imports will create a global variable there, the imports will be
+     * affects the main template directly, as the imports will invoke a global variable there, the imports will be
      * visible from the further imported templates too (note that {@link Configuration#getIncompatibleImprovements()}
      * set to 2.3.24 fixes a rarely surfacing bug with that).
      * 
@@ -2051,7 +2050,7 @@ public class Configurable {
      *       <i>propName1</i>=<i>propValue1</i>, <i>propName2</i>=<i>propValue2</i>, ...
      *       <i>propNameN</i>=<i>propValueN</i>)</tt>,
      *       where
-     *       <tt><i>className</i></tt> is the fully qualified class name of the instance to create (except if we have
+     *       <tt><i>className</i></tt> is the fully qualified class name of the instance to invoke (except if we have
      *       builder class or <tt>INSTANCE</tt> field around, but see that later),
      *       <tt><i>constrArg</i></tt>-s are the values of constructor arguments,
      *       and <tt><i>propName</i>=<i>propValue</i></tt>-s set JavaBean properties (like <tt>x=1</tt> means
@@ -2215,7 +2214,7 @@ public class Configurable {
                         setObjectWrapper(Configuration.getDefaultObjectWrapper(Configuration.VERSION_3_0_0));
                     }
                 } else if ("restricted".equalsIgnoreCase(value)) {
-                    setObjectWrapper(new RestrictedObjectWrapper(Configuration.VERSION_3_0_0));
+                    setObjectWrapper(new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
                 } else {
                     setObjectWrapper((ObjectWrapper) _ObjectBuilderSettingEvaluator.eval(
                                     value, ObjectWrapper.class, false, _SettingEvaluationEnvironment.getCurrent()));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configuration.java b/src/main/java/org/apache/freemarker/core/Configuration.java
index 73e85f1..3d1665a 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -49,7 +49,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.outputformat.UnregisteredOutputFormatException;
@@ -97,7 +97,7 @@ import org.apache.freemarker.core.util._UnmodifiableCompositeSet;
  * <b>The main entry point into the FreeMarker API</b>; encapsulates the configuration settings of FreeMarker,
  * also serves as a central template-loading and caching service.
  *
- * <p>This class is meant to be used in a singleton pattern. That is, you create an instance of this at the beginning of
+ * <p>This class is meant to be used in a singleton pattern. That is, you invoke an instance of this at the beginning of
  * the application life-cycle, set its {@link #setSetting(String, String) configuration settings} there (either with the
  * setter methods like {@link #setTemplateLoader(TemplateLoader)} or by loading a {@code .properties} file), and then
  * use that single instance everywhere in your application. Frequently re-creating {@link Configuration} is a typical
@@ -900,7 +900,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      * {@link MruCacheStorage} instead might be advisable. If you don't want caching at
      * all, use {@link org.apache.freemarker.core.templateresolver.impl.NullCacheStorage} (you can't use {@code null}).
      * 
-     * <p>Note that setting the templateResolver storage will re-create the template templateResolver, so
+     * <p>Note that setting the templateResolver storage will re-invoke the template templateResolver, so
      * all its content will be lost.
      */
     public void setCacheStorage(CacheStorage cacheStorage) {
@@ -2266,7 +2266,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *
      * <p>The values in the map must be thread safe, if you are running templates from multiple threads with
      * this configuration. This means that both the plain Java object and the {@link TemplateModel}-s created from them
-     * by the {@link ObjectWrapper} must be thread safe. (The standard {@link ObjectWrapper}-s of FreeMarker create
+     * by the {@link ObjectWrapper} must be thread safe. (The standard {@link ObjectWrapper}-s of FreeMarker invoke
      * thread safe {@link TemplateModel}-s.) The {@link Map} itself need not be thread-safe.
      * 
      * <p>This setter method has no getter pair because of the tricky relation ship with
@@ -2801,7 +2801,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      * @since 2.3.21
      */
     public static ObjectWrapperAndUnwrapper getDefaultObjectWrapper(Version incompatibleImprovements) {
-        return new DefaultObjectWrapperBuilder(incompatibleImprovements).build();
+        return new DefaultObjectWrapper.Builder(incompatibleImprovements).build();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/CustomAttribute.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CustomAttribute.java b/src/main/java/org/apache/freemarker/core/CustomAttribute.java
index e0f5627..37d7db9 100644
--- a/src/main/java/org/apache/freemarker/core/CustomAttribute.java
+++ b/src/main/java/org/apache/freemarker/core/CustomAttribute.java
@@ -79,7 +79,7 @@ public class CustomAttribute {
     /**
      * This method is invoked when {@link #get()} is invoked without 
      * {@link #set(Object)} being invoked before it to define the value in the 
-     * current scope. Override it to create the attribute value on-demand.  
+     * current scope. Override it to invoke the attribute value on-demand.
      * @return the initial value for the custom attribute. By default returns null.
      */
     protected Object create() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java b/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
index a8a02e4..5793ad3 100644
--- a/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
+++ b/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
@@ -108,7 +108,7 @@ public interface DirectiveCallPlace {
      *            {@link IdentityHashMap}, but then this feature would be slower, while {@code providerIdentity}
      *            mismatches aren't occurring in most applications.)
      * @param objectFactory
-     *            Called when the custom data wasn't yet set, to create its initial value. If this parameter is
+     *            Called when the custom data wasn't yet set, to invoke its initial value. If this parameter is
      *            {@code null} and the custom data wasn't set yet, then {@code null} will be returned. The returned
      *            value of {@link ObjectFactory#createObject()} can be any kind of object, but can't be {@code null}.
      * 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Environment.java b/src/main/java/org/apache/freemarker/core/Environment.java
index d6cfa91..1d1bafc 100644
--- a/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/src/main/java/org/apache/freemarker/core/Environment.java
@@ -105,7 +105,7 @@ public final class Environment extends Configurable {
     private static final Logger LOG = _CoreLogs.RUNTIME;
     private static final Logger LOG_ATTEMPT = _CoreLogs.ATTEMPT;
 
-    // Do not use this object directly; clone it first! DecimalFormat isn't
+    // Do not use this object directly; deepClone it first! DecimalFormat isn't
     // thread-safe.
     private static final DecimalFormat C_NUMBER_FORMAT = new DecimalFormat(
             "0.################",
@@ -1573,7 +1573,7 @@ public final class Environment extends Configurable {
             throw MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e);
         } catch (TemplateValueFormatException e) {
             _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                    "Can't create date/time/datetime format based on format string ",
+                    "Can't invoke date/time/datetime format based on format string ",
                     new _DelayedJQuote(formatString), ". Reason given: ",
                     e.getMessage())
                     .blame(blamedFormatterExp);
@@ -2209,7 +2209,7 @@ public final class Environment extends Configurable {
     /**
      * Returns the read-only hash of globally visible variables. This is the correspondent of FTL <code>.globals</code>
      * hash. That is, you see the variables created with <code>&lt;#global ...&gt;</code>, and the variables of the
-     * data-model. To create new global variables, use {@link #setGlobalVariable setGlobalVariable}.
+     * data-model. To invoke new global variables, use {@link #setGlobalVariable setGlobalVariable}.
      */
     public TemplateHashModel getGlobalVariables() {
         return new TemplateHashModel() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Template.java b/src/main/java/org/apache/freemarker/core/Template.java
index 7b981af..c6328df 100644
--- a/src/main/java/org/apache/freemarker/core/Template.java
+++ b/src/main/java/org/apache/freemarker/core/Template.java
@@ -56,7 +56,7 @@ import org.apache.freemarker.core.util._NullArgumentException;
  * threads.
  * 
  * <p>
- * Typically, you will use {@link Configuration#getTemplate(String)} to create/get {@link Template} objects, so you
+ * Typically, you will use {@link Configuration#getTemplate(String)} to invoke/get {@link Template} objects, so you
  * don't construct them directly. But you can also construct a template from a {@link Reader} or a {@link String} that
  * contains the template source code. But then it's important to know that while the resulting {@link Template} is
  * efficient for later processing, creating a new {@link Template} itself is relatively expensive. So try to re-use

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/TokenMgrError.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TokenMgrError.java b/src/main/java/org/apache/freemarker/core/TokenMgrError.java
index 81282a2..79d0f2a 100644
--- a/src/main/java/org/apache/freemarker/core/TokenMgrError.java
+++ b/src/main/java/org/apache/freemarker/core/TokenMgrError.java
@@ -40,7 +40,7 @@ public class TokenMgrError extends Error {
    static final int LEXICAL_ERROR = 0;
 
    /**
-    * An attempt was made to create a second instance of a static token manager.
+    * An attempt was made to invoke a second instance of a static token manager.
     */
    static final int STATIC_LEXER_ERROR = 1;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java b/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
index e98c104..2769e49 100644
--- a/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -59,7 +59,6 @@ import org.apache.freemarker.core.templateresolver.PathRegexMatcher;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.FTLUtil;
 import org.apache.freemarker.core.util.GenericParseException;
-import org.apache.freemarker.core.util.WriteProtectable;
 import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.core.util._StringUtil;
 
@@ -79,7 +78,8 @@ public class _ObjectBuilderSettingEvaluator {
 
     private static final String BUILD_METHOD_NAME = "build";
 
-    private static final String BUILDER_CLASS_POSTFIX = "Builder";
+    private static final String BUILDER_CLASS_POSTFIX_1 = "$Builder";
+    private static final String BUILDER_CLASS_POSTFIX_2 = "Builder";
 
     private static Map<String,String> SHORTHANDS;
     
@@ -860,30 +860,35 @@ public class _ObjectBuilderSettingEvaluator {
             
             boolean clIsBuilderClass;
             try {
-                cl = _ClassUtil.forName(className + BUILDER_CLASS_POSTFIX);
+                cl = _ClassUtil.forName(className + BUILDER_CLASS_POSTFIX_1);
                 clIsBuilderClass = true;
-            } catch (ClassNotFoundException e) {
-                clIsBuilderClass = false;
+            } catch (ClassNotFoundException eIgnored) {
                 try {
-                    cl = _ClassUtil.forName(className);
-                } catch (Exception e2) {
-                    boolean failedToGetAsStaticField;
-                    if (canBeStaticField) {
-                        // Try to interpret className as static filed: 
-                        try {
-                            return getStaticFieldValue(className);
-                        } catch (_ObjectBuilderSettingEvaluationException e3) {
-                            // Suppress it
-                            failedToGetAsStaticField = true;
+                    cl = _ClassUtil.forName(className + BUILDER_CLASS_POSTFIX_2);
+                    clIsBuilderClass = true;
+                } catch (ClassNotFoundException e) {
+                    clIsBuilderClass = false;
+                    try {
+                        cl = _ClassUtil.forName(className);
+                    } catch (Exception e2) {
+                        boolean failedToGetAsStaticField;
+                        if (canBeStaticField) {
+                            // Try to interpret className as static filed:
+                            try {
+                                return getStaticFieldValue(className);
+                            } catch (_ObjectBuilderSettingEvaluationException e3) {
+                                // Suppress it
+                                failedToGetAsStaticField = true;
+                            }
+                        } else {
+                            failedToGetAsStaticField = false;
                         }
-                    } else {
-                        failedToGetAsStaticField = false;
+                        throw new _ObjectBuilderSettingEvaluationException(
+                                "Failed to get class " + _StringUtil.jQuote(className)
+                                        + (failedToGetAsStaticField ? " (also failed to resolve name as static field)" : "")
+                                        + ".",
+                                e2);
                     }
-                    throw new _ObjectBuilderSettingEvaluationException(
-                            "Failed to get class " + _StringUtil.jQuote(className)
-                            + (failedToGetAsStaticField ? " (also failed to resolve name as static field)" : "")
-                            + ".",
-                            e2);
                 }
             }
             
@@ -909,17 +914,7 @@ public class _ObjectBuilderSettingEvaluator {
             // Named parameters will set JavaBeans properties:
             setJavaBeanProperties(constructorResult, namedParamNames, namedParamValues);
 
-            final Object result;
-            if (clIsBuilderClass) {
-                result = callBuild(constructorResult);
-            } else {
-                if (constructorResult instanceof WriteProtectable) {
-                    ((WriteProtectable) constructorResult).writeProtect();
-                }
-                result = constructorResult;
-            }
-            
-            return result;
+            return clIsBuilderClass ? callBuild(constructorResult) : constructorResult;
         }
         
         private Object getStaticFieldValue(String dottedName) throws _ObjectBuilderSettingEvaluationException {
@@ -972,7 +967,7 @@ public class _ObjectBuilderSettingEvaluator {
         private Object callConstructor(Class cl)
                 throws _ObjectBuilderSettingEvaluationException {
             if (hasNoParameters()) {
-                // No need to create ObjectWrapper
+                // No need to invoke ObjectWrapper
                 try {
                     return cl.newInstance();
                 } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java b/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
index aaca144..9501185 100644
--- a/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
+++ b/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
@@ -53,7 +53,7 @@ public class _SettingEvaluationEnvironment {
 
     public DefaultObjectWrapper getObjectWrapper() {
         if (objectWrapper == null) {
-            objectWrapper = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+            objectWrapper = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         }
         return objectWrapper;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
index fbee3b1..8f83eca 100644
--- a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
@@ -41,7 +41,6 @@ import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.SimpleCollection;
 import org.apache.freemarker.core.model.impl.SimpleScalar;
 import org.apache.freemarker.core.util.UndeclaredThrowableException;
@@ -57,7 +56,7 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
     private static long nextId = 1;
     private static Set remotes = new HashSet();
 
-    private static final DefaultObjectWrapper OBJECT_WRAPPER = new DefaultObjectWrapperBuilder(Configuration
+    private static final DefaultObjectWrapper OBJECT_WRAPPER = new DefaultObjectWrapper.Builder(Configuration
             .VERSION_3_0_0)
             .build();
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
index 7e263d8..28985ec 100644
--- a/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
+++ b/src/main/java/org/apache/freemarker/core/debug/RmiDebuggerListenerImpl.java
@@ -31,7 +31,7 @@ import org.apache.freemarker.core.debug.EnvironmentSuspendedEvent;
 import org.slf4j.Logger;
 
 /**
- * Used by the {@link DebuggerClient} to create local 
+ * Used by the {@link DebuggerClient} to invoke local
  */
 class RmiDebuggerListenerImpl
 extends

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/TemplateMethodModelEx.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/TemplateMethodModelEx.java b/src/main/java/org/apache/freemarker/core/model/TemplateMethodModelEx.java
index d1d3cc0..2517d22 100644
--- a/src/main/java/org/apache/freemarker/core/model/TemplateMethodModelEx.java
+++ b/src/main/java/org/apache/freemarker/core/model/TemplateMethodModelEx.java
@@ -26,7 +26,7 @@ import org.apache.freemarker.core.util.DeepUnwrap;
 
 /**
  * "extended method" template language data type: Objects that act like functions. Their main application is calling
- * Java methods via {@link org.apache.freemarker.core.model.impl.DefaultObjectWrapper}, but you can implement this interface to create
+ * Java methods via {@link org.apache.freemarker.core.model.impl.DefaultObjectWrapper}, but you can implement this interface to invoke
  * top-level functions too. They are "extended" compared to the deprecated {@link TemplateMethodModel}, which could only
  * accept string parameters.
  * 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java b/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
index 556660b..d630752 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
@@ -114,7 +114,7 @@ public class BeanModel
      * (Side-note: this also implies that any class whose method has been called
      * will be strongly referred to by the framework and will not become
      * unloadable until this class has been unloaded first. Normally this is not
-     * an issue, but can be in a rare scenario where you create many classes on-
+     * an issue, but can be in a rare scenario where you invoke many classes on-
      * the-fly. Also, as the cache grows with new classes and methods introduced
      * to the framework, it may appear as if it were leaking memory. The
      * framework does, however detect class reloads (if you happen to be in an
@@ -137,29 +137,11 @@ public class BeanModel
         TemplateModel retval = null;
         
         try {
-            if (wrapper.isMethodsShadowItems()) {
-                Object fd = classInfo.get(key);
-                if (fd != null) {
-                    retval = invokeThroughDescriptor(fd, classInfo);
-                } else {
-                    retval = invokeGenericGet(classInfo, clazz, key);
-                }
+            Object fd = classInfo.get(key);
+            if (fd != null) {
+                retval = invokeThroughDescriptor(fd, classInfo);
             } else {
-                TemplateModel model = invokeGenericGet(classInfo, clazz, key);
-                final TemplateModel nullModel = wrapper.wrap(null);
-                if (model != nullModel && model != UNKNOWN) {
-                    return model;
-                }
-                Object fd = classInfo.get(key);
-                if (fd != null) {
-                    retval = invokeThroughDescriptor(fd, classInfo);
-                    if (retval == UNKNOWN && model == nullModel) {
-                        // This is the (somewhat subtle) case where the generic get() returns null
-                        // and we have no bean info, so we respect the fact that
-                        // the generic get() returns null and return null. (JR)
-                        retval = nullModel;
-                    }
-                }
+                retval = invokeGenericGet(classInfo, clazz, key);
             }
             if (retval == UNKNOWN) {
                 if (wrapper.isStrict()) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
index c19de75..303672a 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -47,8 +47,10 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.freemarker.core.Version;
 import org.apache.freemarker.core._CoreLogs;
 import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util.BuilderBase;
 import org.apache.freemarker.core.util._JavaVersions;
 import org.apache.freemarker.core.util._NullArgumentException;
 import org.slf4j.Logger;
@@ -161,17 +163,17 @@ class ClassIntrospector {
      * @param pa
      *            Stores what the values of the JavaBean properties of the returned instance will be. Not {@code null}.
      */
-    ClassIntrospector(ClassIntrospectorBuilder pa, Object sharedLock) {
+    ClassIntrospector(Builder pa, Object sharedLock) {
         this(pa, sharedLock, false, false);
     }
 
     /**
      * @param hasSharedInstanceRestrictons
-     *            {@code true} exactly if we are creating a new instance with {@link ClassIntrospectorBuilder}. Then
+     *            {@code true} exactly if we are creating a new instance with {@link Builder}. Then
      *            it's {@code true} even if it won't put the instance into the cache.
      */
-    ClassIntrospector(ClassIntrospectorBuilder builder, Object sharedLock,
-            boolean hasSharedInstanceRestrictons, boolean shared) {
+    ClassIntrospector(Builder builder, Object sharedLock,
+                      boolean hasSharedInstanceRestrictons, boolean shared) {
         _NullArgumentException.check("sharedLock", sharedLock);
 
         exposureLevel = builder.getExposureLevel();
@@ -190,11 +192,11 @@ class ClassIntrospector {
     }
 
     /**
-     * Returns a {@link ClassIntrospectorBuilder}-s that could be used to create an identical {@link #ClassIntrospector}
-     * . The returned {@link ClassIntrospectorBuilder} can be modified without interfering with anything.
+     * Returns a {@link Builder}-s that could be used to invoke an identical {@link #ClassIntrospector}
+     * . The returned {@link Builder} can be modified without interfering with anything.
      */
-    ClassIntrospectorBuilder createBuilder() {
-        return new ClassIntrospectorBuilder(this);
+    Builder createBuilder() {
+        return new Builder(this);
     }
 
     // ------------------------------------------------------------------------------------------------------------------
@@ -1037,7 +1039,7 @@ class ClassIntrospector {
     }
 
     /**
-     * Returns {@code true} if this instance was created with {@link ClassIntrospectorBuilder}, even if it wasn't
+     * Returns {@code true} if this instance was created with {@link Builder}, even if it wasn't
      * actually put into the cache (as we reserve the right to do so in later versions).
      */
     boolean getHasSharedInstanceRestrictons() {
@@ -1071,4 +1073,164 @@ class ClassIntrospector {
         }
     }
 
+    static final class Builder extends BuilderBase<ClassIntrospector, Builder> implements Cloneable {
+
+        private static final Map/*<PropertyAssignments, Reference<ClassIntrospector>>*/ INSTANCE_CACHE = new HashMap();
+        private static final ReferenceQueue INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue();
+
+        // Properties and their *defaults*:
+        private int exposureLevel = DefaultObjectWrapper.EXPOSE_SAFE;
+        private boolean exposeFields;
+        private MethodAppearanceFineTuner methodAppearanceFineTuner;
+        private MethodSorter methodSorter;
+        // Attention:
+        // - This is also used as a cache key, so non-normalized field values should be avoided.
+        // - If some field has a default value, it must be set until the end of the constructor. No field that has a
+        //   default can be left unset (like null).
+        // - If you add a new field, review all methods in this class, also the ClassIntrospector constructor
+
+        Builder(ClassIntrospector ci) {
+            exposureLevel = ci.exposureLevel;
+            exposeFields = ci.exposeFields;
+            methodAppearanceFineTuner = ci.methodAppearanceFineTuner;
+            methodSorter = ci.methodSorter;
+        }
+
+        Builder(Version incompatibleImprovements) {
+            // Warning: incompatibleImprovements must not affect this object at versions increments where there's no
+            // change in the DefaultObjectWrapper.normalizeIncompatibleImprovements results. That is, this class may don't react
+            // to some version changes that affects DefaultObjectWrapper, but not the other way around.
+            _NullArgumentException.check(incompatibleImprovements);
+            // Currently nothing depends on incompatibleImprovements
+        }
+
+        @Override
+        protected Object clone() {
+            try {
+                return super.clone();
+            } catch (CloneNotSupportedException e) {
+                throw new RuntimeException("Failed to deepClone Builder", e);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (exposeFields ? 1231 : 1237);
+            result = prime * result + exposureLevel;
+            result = prime * result + System.identityHashCode(methodAppearanceFineTuner);
+            result = prime * result + System.identityHashCode(methodSorter);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (obj == null) return false;
+            if (getClass() != obj.getClass()) return false;
+            Builder other = (Builder) obj;
+
+            if (exposeFields != other.exposeFields) return false;
+            if (exposureLevel != other.exposureLevel) return false;
+            if (methodAppearanceFineTuner != other.methodAppearanceFineTuner) return false;
+            return methodSorter == other.methodSorter;
+        }
+
+        public int getExposureLevel() {
+            return exposureLevel;
+        }
+
+        /** See {@link DefaultObjectWrapper#setExposureLevel(int)}. */
+        public void setExposureLevel(int exposureLevel) {
+            if (exposureLevel < DefaultObjectWrapper.EXPOSE_ALL || exposureLevel > DefaultObjectWrapper.EXPOSE_NOTHING) {
+                throw new IllegalArgumentException("Illegal exposure level: " + exposureLevel);
+            }
+
+            this.exposureLevel = exposureLevel;
+        }
+
+        public boolean getExposeFields() {
+            return exposeFields;
+        }
+
+        /** See {@link DefaultObjectWrapper#setExposeFields(boolean)}. */
+        public void setExposeFields(boolean exposeFields) {
+            this.exposeFields = exposeFields;
+        }
+
+        public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
+            return methodAppearanceFineTuner;
+        }
+
+        public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) {
+            this.methodAppearanceFineTuner = methodAppearanceFineTuner;
+        }
+
+        public MethodSorter getMethodSorter() {
+            return methodSorter;
+        }
+
+        public void setMethodSorter(MethodSorter methodSorter) {
+            this.methodSorter = methodSorter;
+        }
+
+        private static void removeClearedReferencesFromInstanceCache() {
+            Reference clearedRef;
+            while ((clearedRef = INSTANCE_CACHE_REF_QUEUE.poll()) != null) {
+                synchronized (INSTANCE_CACHE) {
+                    findClearedRef: for (Iterator it = INSTANCE_CACHE.values().iterator(); it.hasNext(); ) {
+                        if (it.next() == clearedRef) {
+                            it.remove();
+                            break findClearedRef;
+                        }
+                    }
+                }
+            }
+        }
+
+        /** For unit testing only */
+        static void clearInstanceCache() {
+            synchronized (INSTANCE_CACHE) {
+                INSTANCE_CACHE.clear();
+            }
+        }
+
+        /** For unit testing only */
+        static Map getInstanceCache() {
+            return INSTANCE_CACHE;
+        }
+
+        /**
+         * Returns an instance that is possibly shared (singleton). Note that this comes with its own "shared lock",
+         * since everyone who uses this object will have to lock with that common object.
+         */
+        @Override
+        public ClassIntrospector build() {
+            if ((methodAppearanceFineTuner == null || methodAppearanceFineTuner instanceof SingletonCustomizer)
+                    && (methodSorter == null || methodSorter instanceof SingletonCustomizer)) {
+                // Instance can be cached.
+                ClassIntrospector instance;
+                synchronized (INSTANCE_CACHE) {
+                    Reference instanceRef = (Reference) INSTANCE_CACHE.get(this);
+                    instance = instanceRef != null ? (ClassIntrospector) instanceRef.get() : null;
+                    if (instance == null) {
+                        Builder thisClone = (Builder) clone();  // prevent any aliasing issues
+                        instance = new ClassIntrospector(thisClone, new Object(), true, true);
+                        INSTANCE_CACHE.put(thisClone, new WeakReference(instance, INSTANCE_CACHE_REF_QUEUE));
+                    }
+                }
+
+                removeClearedReferencesFromInstanceCache();
+
+                return instance;
+            } else {
+                // If methodAppearanceFineTuner or methodSorter is specified and isn't marked as a singleton, the
+                // ClassIntrospector can't be shared/cached as those objects could contain a back-reference to the
+                // DefaultObjectWrapper.
+                return new ClassIntrospector(this, new Object(), true, false);
+            }
+        }
+
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospectorBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospectorBuilder.java b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospectorBuilder.java
deleted file mode 100644
index 4a26a9c..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospectorBuilder.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.util._NullArgumentException;
-
-final class ClassIntrospectorBuilder implements Cloneable {
-
-    private static final Map/*<PropertyAssignments, Reference<ClassIntrospector>>*/ INSTANCE_CACHE = new HashMap();
-    private static final ReferenceQueue INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue(); 
-    
-    // Properties and their *defaults*:
-    private int exposureLevel = DefaultObjectWrapper.EXPOSE_SAFE;
-    private boolean exposeFields;
-    private MethodAppearanceFineTuner methodAppearanceFineTuner;
-    private MethodSorter methodSorter;
-    // Attention:
-    // - This is also used as a cache key, so non-normalized field values should be avoided.
-    // - If some field has a default value, it must be set until the end of the constructor. No field that has a
-    //   default can be left unset (like null).
-    // - If you add a new field, review all methods in this class, also the ClassIntrospector constructor
-    
-    ClassIntrospectorBuilder(ClassIntrospector ci) {
-        exposureLevel = ci.exposureLevel;
-        exposeFields = ci.exposeFields;
-        methodAppearanceFineTuner = ci.methodAppearanceFineTuner;
-        methodSorter = ci.methodSorter; 
-    }
-    
-    ClassIntrospectorBuilder(Version incompatibleImprovements) {
-        // Warning: incompatibleImprovements must not affect this object at versions increments where there's no
-        // change in the DefaultObjectWrapper.normalizeIncompatibleImprovements results. That is, this class may don't react
-        // to some version changes that affects DefaultObjectWrapper, but not the other way around.
-        _NullArgumentException.check(incompatibleImprovements);
-        // Currently nothing depends on incompatibleImprovements
-    }
-    
-    @Override
-    protected Object clone() {
-        try {
-            return super.clone();
-        } catch (CloneNotSupportedException e) {
-            throw new RuntimeException("Failed to clone ClassIntrospectorBuilder", e);
-        }
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + (exposeFields ? 1231 : 1237);
-        result = prime * result + exposureLevel;
-        result = prime * result + System.identityHashCode(methodAppearanceFineTuner);
-        result = prime * result + System.identityHashCode(methodSorter);
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) return true;
-        if (obj == null) return false;
-        if (getClass() != obj.getClass()) return false;
-        ClassIntrospectorBuilder other = (ClassIntrospectorBuilder) obj;
-        
-        if (exposeFields != other.exposeFields) return false;
-        if (exposureLevel != other.exposureLevel) return false;
-        if (methodAppearanceFineTuner != other.methodAppearanceFineTuner) return false;
-        return methodSorter == other.methodSorter;
-    }
-    
-    public int getExposureLevel() {
-        return exposureLevel;
-    }
-
-    /** See {@link DefaultObjectWrapper#setExposureLevel(int)}. */
-    public void setExposureLevel(int exposureLevel) {
-        if (exposureLevel < DefaultObjectWrapper.EXPOSE_ALL || exposureLevel > DefaultObjectWrapper.EXPOSE_NOTHING) {
-            throw new IllegalArgumentException("Illegal exposure level: " + exposureLevel);
-        }
-        
-        this.exposureLevel = exposureLevel;
-    }
-
-    public boolean getExposeFields() {
-        return exposeFields;
-    }
-
-    /** See {@link DefaultObjectWrapper#setExposeFields(boolean)}. */
-    public void setExposeFields(boolean exposeFields) {
-        this.exposeFields = exposeFields;
-    }
-
-    public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
-        return methodAppearanceFineTuner;
-    }
-
-    public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) {
-        this.methodAppearanceFineTuner = methodAppearanceFineTuner;
-    }
-
-    public MethodSorter getMethodSorter() {
-        return methodSorter;
-    }
-
-    public void setMethodSorter(MethodSorter methodSorter) {
-        this.methodSorter = methodSorter;
-    }
-
-    private static void removeClearedReferencesFromInstanceCache() {
-        Reference clearedRef;
-        while ((clearedRef = INSTANCE_CACHE_REF_QUEUE.poll()) != null) {
-            synchronized (INSTANCE_CACHE) {
-                findClearedRef: for (Iterator it = INSTANCE_CACHE.values().iterator(); it.hasNext(); ) {
-                    if (it.next() == clearedRef) {
-                        it.remove();
-                        break findClearedRef;
-                    }
-                }
-            }
-        }
-    }
-
-    /** For unit testing only */
-    static void clearInstanceCache() {
-        synchronized (INSTANCE_CACHE) {
-            INSTANCE_CACHE.clear();
-        }
-    }
-    
-    /** For unit testing only */
-    static Map getInstanceCache() {
-        return INSTANCE_CACHE;
-    }
-
-    /**
-     * Returns an instance that is possibly shared (singleton). Note that this comes with its own "shared lock",
-     * since everyone who uses this object will have to lock with that common object.
-     */
-    ClassIntrospector build() {
-        if ((methodAppearanceFineTuner == null || methodAppearanceFineTuner instanceof SingletonCustomizer)
-                && (methodSorter == null || methodSorter instanceof SingletonCustomizer)) {
-            // Instance can be cached.
-            ClassIntrospector instance;
-            synchronized (INSTANCE_CACHE) {
-                Reference instanceRef = (Reference) INSTANCE_CACHE.get(this);
-                instance = instanceRef != null ? (ClassIntrospector) instanceRef.get() : null;
-                if (instance == null) {
-                    ClassIntrospectorBuilder thisClone = (ClassIntrospectorBuilder) clone();  // prevent any aliasing issues
-                    instance = new ClassIntrospector(thisClone, new Object(), true, true);
-                    INSTANCE_CACHE.put(thisClone, new WeakReference(instance, INSTANCE_CACHE_REF_QUEUE));
-                }
-            }
-            
-            removeClearedReferencesFromInstanceCache();
-            
-            return instance;
-        } else {
-            // If methodAppearanceFineTuner or methodSorter is specified and isn't marked as a singleton, the
-            // ClassIntrospector can't be shared/cached as those objects could contain a back-reference to the
-            // DefaultObjectWrapper.
-            return new ClassIntrospector(this, new Object(), true, false);
-        }
-    }
-    
-}
\ No newline at end of file


[2/4] incubator-freemarker git commit: Made DefaultObjectWrapper immtutable (no more setters), also removed public constructors. Instead, instances are created with DefaultObjectWrapper.Builder.build(). Reworked DefaultObjectWrapper and ClassIntrospector

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
index f64138a..fbe7d82 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/TemplateLookupContext.java
@@ -28,7 +28,7 @@ import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStr
 
 /**
  * Used as the parameter of {@link TemplateLookupStrategy#lookup(TemplateLookupContext)}.
- * You can't create instances of this, only receive them from FreeMarker.
+ * You can't invoke instances of this, only receive them from FreeMarker.
  * 
  * @since 2.3.22
  */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java b/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java
index cfcbf44..fe7a54c 100644
--- a/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java
+++ b/src/main/java/org/apache/freemarker/core/templateresolver/impl/TemplateLoaderBasedTemplateLookupResult.java
@@ -26,9 +26,9 @@ import org.apache.freemarker.core.util._NullArgumentException;
 
 /**
  * Class of {@link TemplateLookupResult} instances created by {@link TemplateLoaderBasedTemplateLookupContext}. To
- * create instances of this inside your own {@link TemplateLoaderBasedTemplateLookupContext} subclass, call
+ * invoke instances of this inside your own {@link TemplateLoaderBasedTemplateLookupContext} subclass, call
  * {@link TemplateLoaderBasedTemplateLookupContext#createLookupResult(String, TemplateLoadingResult)} and
- * {@link TemplateLoaderBasedTemplateLookupContext#createNegativeLookupResult()}. You should not try to create instances
+ * {@link TemplateLoaderBasedTemplateLookupContext#createNegativeLookupResult()}. You should not try to invoke instances
  * anywhere else. Also, this class deliberately can't be subclassed (except inside FreeMarker).
  */
 public abstract class TemplateLoaderBasedTemplateLookupResult extends TemplateLookupResult {
@@ -38,7 +38,7 @@ public abstract class TemplateLoaderBasedTemplateLookupResult extends TemplateLo
         return NegativeTemplateLookupResult.INSTANCE;
     }
     
-    /** Used internally to create the appropriate kind of result from the parameters. */
+    /** Used internally to invoke the appropriate kind of result from the parameters. */
     static TemplateLoaderBasedTemplateLookupResult from(String templateSourceName, TemplateLoadingResult templateLoaderResult) {
         return templateLoaderResult.getStatus() != TemplateLoadingResultStatus.NOT_FOUND
                 ? new PositiveTemplateLookupResult(templateSourceName, templateLoaderResult)

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/util/BuilderBase.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/BuilderBase.java b/src/main/java/org/apache/freemarker/core/util/BuilderBase.java
new file mode 100644
index 0000000..c8eb12c
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/util/BuilderBase.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.util;
+
+/**
+ * Common superclass of builders (used for implementing the builder pattern).
+ */
+public abstract class BuilderBase<ProductT, SelfT extends BuilderBase<ProductT, SelfT>> {
+
+    public abstract ProductT build();
+
+    @SuppressWarnings("unchecked")
+    protected SelfT self() {
+        return (SelfT) this;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java b/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java
new file mode 100644
index 0000000..7c100bb
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/util/ProductWrappingBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.util;
+
+/**
+ * A builder that encloses an already built product. {@link #build()} will always return the same product object.
+ */
+public class ProductWrappingBuilder<ProductT, SelfT extends ProductWrappingBuilder<ProductT, SelfT>>
+        extends BuilderBase<ProductT, SelfT> {
+
+    private final ProductT product;
+
+    public ProductWrappingBuilder(ProductT product) {
+        _NullArgumentException.check("product", product);
+        this.product = product;
+    }
+
+    @Override
+    public ProductT build() {
+        return product;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/util/WriteProtectable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/WriteProtectable.java b/src/main/java/org/apache/freemarker/core/util/WriteProtectable.java
deleted file mode 100644
index dc94cb8..0000000
--- a/src/main/java/org/apache/freemarker/core/util/WriteProtectable.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.util;
-
-/**
- * Implemented by objects that can be made <em>permanently</em> read-only. This usually meant to freeze the
- * configuration JavaBean properties, so that the object can be safely shared among independently developed components.
- * 
- * @since 2.3.21
- */
-public interface WriteProtectable {
-
-    /**
-     * Makes this object permanently read-only.
-     */
-    void writeProtect();
-    
-    boolean isWriteProtected();
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/util/_DateUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_DateUtil.java b/src/main/java/org/apache/freemarker/core/util/_DateUtil.java
index 560fec9..0cf2fea 100644
--- a/src/main/java/org/apache/freemarker/core/util/_DateUtil.java
+++ b/src/main/java/org/apache/freemarker/core/util/_DateUtil.java
@@ -235,7 +235,7 @@ public class _DateUtil {
      *        that although date-only formats has no time zone offset part,
      *        the result still depends on the time zone, as days start and end
      *        at different points on the time line in different zones.      
-     * @param calendarFactory the factory that will create the calendar used
+     * @param calendarFactory the factory that will invoke the calendar used
      *        internally for calculations. The point of this parameter is that
      *        creating a new calendar is relatively expensive, so it's desirable
      *        to reuse calendars and only set their time and zone. (This was

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.java b/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.java
index 4ce5461..b4625a4 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.java
@@ -21,7 +21,7 @@ package org.apache.freemarker.core.valueformat.impl;
 import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
 
 /**
- * Can't create a template format that the template format refers to (typically thrown by alias template formats).
+ * Can't invoke a template format that the template format refers to (typically thrown by alias template formats).
  * 
  * @since 2.3.24
  */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java b/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java
index b366791..a964bc2 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java
@@ -89,7 +89,7 @@ public final class AliasTemplateDateFormatFactory extends TemplateDateFormatFact
             }
             return env.getTemplateDateFormat(targetFormatString, dateType, locale, timeZone, zonelessInput);
         } catch (TemplateValueFormatException e) {
-            throw new AliasTargetTemplateValueFormatException("Failed to create format based on target format string,  "
+            throw new AliasTargetTemplateValueFormatException("Failed to invoke format based on target format string,  "
                     + _StringUtil.jQuote(params) + ". Reason given: " + e.getMessage(), e);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java b/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java
index 2c2a9b0..72e8abd 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java
@@ -88,7 +88,7 @@ public final class AliasTemplateNumberFormatFactory extends TemplateNumberFormat
             }
             return env.getTemplateNumberFormat(targetFormatString, locale);
         } catch (TemplateValueFormatException e) {
-            throw new AliasTargetTemplateValueFormatException("Failed to create format based on target format string,  "
+            throw new AliasTargetTemplateValueFormatException("Failed to invoke format based on target format string,  "
                     + _StringUtil.jQuote(params) + ". Reason given: " + e.getMessage(), e);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/core/valueformat/impl/JavaTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/impl/JavaTemplateNumberFormatFactory.java b/src/main/java/org/apache/freemarker/core/valueformat/impl/JavaTemplateNumberFormatFactory.java
index 41dd373..cf292df 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/impl/JavaTemplateNumberFormatFactory.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/impl/JavaTemplateNumberFormatFactory.java
@@ -100,7 +100,7 @@ public class JavaTemplateNumberFormatFactory extends TemplateNumberFormatFactory
             }
         }  // if cache miss
         
-        // JFormat-s aren't thread-safe; must clone it
+        // JFormat-s aren't thread-safe; must deepClone it
         jFormat = (NumberFormat) jFormat.clone();
         
         return new JavaTemplateNumberFormat(jFormat, params);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java b/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
index 95b139e..0fc7cfa 100644
--- a/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
+++ b/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
@@ -752,14 +752,14 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     /**
-     * Create the template loader. The default implementation will create a {@link ClassTemplateLoader} if the template
+     * Create the template loader. The default implementation will invoke a {@link ClassTemplateLoader} if the template
      * path starts with {@code "class://"}, a {@link FileTemplateLoader} if the template path starts with
      * {@code "file://"}, and a {@link WebAppTemplateLoader} otherwise. Also, if
      * {@link Configuration#Configuration(org.apache.freemarker.core.Version) incompatible_improvements} is 2.3.22 or higher,
-     * it will create a {@link MultiTemplateLoader} if the template path starts with {@code "["}.
+     * it will invoke a {@link MultiTemplateLoader} if the template path starts with {@code "["}.
      * 
      * @param templatePath
-     *            the template path to create a loader for
+     *            the template path to invoke a loader for
      * @return a newly created template loader
      */
     protected TemplateLoader createTemplateLoader(String templatePath) throws IOException {
@@ -1082,7 +1082,7 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     /**
-     * Called to create the {@link TaglibFactory} once per servlet context.
+     * Called to invoke the {@link TaglibFactory} once per servlet context.
      * The default implementation configures it based on the servlet-init parameters and various other environmental
      * settings, so if you override this method, you should call super, then adjust the result.
      * 
@@ -1276,7 +1276,7 @@ public class FreemarkerServlet extends HttpServlet {
     }
     
     /**
-     * Called from {@link #init()} to create the {@link ObjectWrapper}; to customzie this aspect, in most cases you
+     * Called from {@link #init()} to invoke the {@link ObjectWrapper}; to customzie this aspect, in most cases you
      * should override {@link #createDefaultObjectWrapper()} instead. Overriding this method is necessary when you want
      * to customize how the {@link ObjectWrapper} is created <em>from the init-param values</em>, or you want to do some
      * post-processing (like checking) on the created {@link ObjectWrapper}. To customize init-param interpretation,
@@ -1301,7 +1301,7 @@ public class FreemarkerServlet extends HttpServlet {
                         + DEPR_INITPARAM_OBJECT_WRAPPER);
             }
             if (DEPR_INITPARAM_WRAPPER_RESTRICTED.equals(wrapper)) {
-                return new RestrictedObjectWrapper(Configuration.VERSION_3_0_0);
+                return new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
             }
             return createDefaultObjectWrapper();
         } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/servlet/IncludePage.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/IncludePage.java b/src/main/java/org/apache/freemarker/servlet/IncludePage.java
index 30456ca..608f1bb 100644
--- a/src/main/java/org/apache/freemarker/servlet/IncludePage.java
+++ b/src/main/java/org/apache/freemarker/servlet/IncludePage.java
@@ -100,7 +100,7 @@ public class IncludePage implements TemplateDirectiveModel {
             final PrintWriter printWriter = (envOut instanceof PrintWriter) ?
                 (PrintWriter) envOut :
                 new PrintWriter(envOut); 
-            // Otherwise, create a response wrapper that will pass the
+            // Otherwise, invoke a response wrapper that will pass the
             // env writer, potentially first wrapping it in a print
             // writer when it ain't one already.
             wrappedResponse = new HttpServletResponseWrapper(response) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java b/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
index a0d2cb6..b005260 100644
--- a/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
+++ b/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
@@ -37,7 +37,7 @@ import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._ClassUtil;
 
 /**
- * Used when a custom JSP tag and an EL function uses the same name in a tag library, to create a single FTL value from
+ * Used when a custom JSP tag and an EL function uses the same name in a tag library, to invoke a single FTL value from
  * the two. As FTL as no separate namespace for "tags" and functions, both aspect has to be implemented by the same
  * value.
  * 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java b/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
index f38918d..ec4a5d0 100644
--- a/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
+++ b/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
@@ -1809,7 +1809,7 @@ public class TaglibFactory implements TemplateHashModel {
                         listener = listenerClass.newInstance();
                     } catch (Exception e) {
                         throw new TldParsingSAXException(
-                                "Failed to create new instantiate from listener class " + listenerClassCData,
+                                "Failed to invoke new instantiate from listener class " + listenerClassCData,
                                 locator,
                                 e);
                     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/main/java/org/apache/freemarker/servlet/jsp/_FreeMarkerPageContext21.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/jsp/_FreeMarkerPageContext21.java b/src/main/java/org/apache/freemarker/servlet/jsp/_FreeMarkerPageContext21.java
index f1d1fd0..a146c01 100644
--- a/src/main/java/org/apache/freemarker/servlet/jsp/_FreeMarkerPageContext21.java
+++ b/src/main/java/org/apache/freemarker/servlet/jsp/_FreeMarkerPageContext21.java
@@ -110,7 +110,7 @@ public class _FreeMarkerPageContext21 extends FreeMarkerPageContext {
                 elContext.putContext(JspContext.class, this);
             } else {
                 throw new UnsupportedOperationException(
-                        "Can not create an ELContext using a foreign JspApplicationContext (of class "
+                        "Can not invoke an ELContext using a foreign JspApplicationContext (of class "
                         + _ClassUtil.getShortClassNameOfObject(jspctx) + ").\n" +
                         "Hint: The cause of this is often that you are trying to use JSTL tags/functions in FTL. "
                         + "In that case, know that that's not really suppored, and you are supposed to use FTL "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/manual/en_US/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/src/manual/en_US/FM3-CHANGE-LOG.txt b/src/manual/en_US/FM3-CHANGE-LOG.txt
index f59ed0b..e15d2b5 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -150,4 +150,10 @@ the FreeMarer 3 changelog here:
   read method can still be called as an usual method (as `myObj.getFoo(index)`). These changes were made because building on the
   indexed read method we can't create a proper sequence (which the value of the property should be), since sequences are required
   to support returning their size. (In FreeMarker 2 such sequences has thrown exception on calling size(), which caused more
-  problems and confusion than it solved.)
\ No newline at end of file
+  problems and confusion than it solved.)
+- When looking for a builder class in builder expressions used in setting values like `com.example.Foo()`, now we first
+  look for com.example.Foo.Builder, and only then com.example.FooBuilder.
+- Removed DefaultObjectWrapper.methodsShadowItems setting, in effect defaulting it to true. This has decided if the generic
+  get method (`get(String)`) had priority over methods of similar name. The generic get method is only recognized from its
+  name and parameter type, so it's a quite consfusing feature, and might will be removed alltogether.
+- DefaultObjectWrapper is not immutable (has no setter methods), and can only be constructed with DefaultObjectWrapper.Builder.n
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/ASTPrinter.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ASTPrinter.java b/src/test/java/org/apache/freemarker/core/ASTPrinter.java
index a80e1f1..10701cf 100644
--- a/src/test/java/org/apache/freemarker/core/ASTPrinter.java
+++ b/src/test/java/org/apache/freemarker/core/ASTPrinter.java
@@ -195,7 +195,7 @@ public class ASTPrinter {
     private void save(String astStr, File file) throws IOException {
         File parentDir = file.getParentFile();
         if (!parentDir.isDirectory() && !parentDir.mkdirs()) {
-            throw new IOException("Failed to create parent directory: " + parentDir);
+            throw new IOException("Failed to invoke parent directory: " + parentDir);
         }
         
         Writer w = new BufferedWriter(new FileWriter(file));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index 0ad4773..6ae7396 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -40,7 +40,6 @@ import java.util.TimeZone;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
 import org.apache.freemarker.core.model.impl.SimpleScalar;
 import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
@@ -109,7 +108,7 @@ public class ConfigurationTest extends TestCase {
         assertFalse(cfg.isObjectWrapperExplicitlySet());
         assertSame(Configuration.getDefaultObjectWrapper(Configuration.VERSION_3_0_0), cfg.getObjectWrapper());
         //
-        RestrictedObjectWrapper ow = new RestrictedObjectWrapper(Configuration.VERSION_3_0_0);
+        RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         cfg.setObjectWrapper(ow);
         assertTrue(cfg.isObjectWrapperExplicitlySet());
         assertSame(ow, cfg.getObjectWrapper());
@@ -1075,7 +1074,7 @@ public class ConfigurationTest extends TestCase {
         cfg.setSharedVariable("b", "bbLegacy");
         
         // Cause re-wrapping of variables added via setSharedVaribles:
-        cfg.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build());
+        cfg.setObjectWrapper(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
 
         {
             TemplateScalarModel aVal = (TemplateScalarModel) cfg.getSharedVariable("a");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
index 9df5840..d35003d 100644
--- a/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
+++ b/src/test/java/org/apache/freemarker/core/IncludeAndImportTest.java
@@ -78,7 +78,7 @@ public class IncludeAndImportTest extends TemplateTest {
     
     @Test
     public void importInMainCreatesGlobalBugfix() throws IOException, TemplateException {
-        // An import in the main namespace should create a global variable, even if the imported library was already
+        // An import in the main namespace should invoke a global variable, even if the imported library was already
         // initialized elsewhere.
         String ftl = "<#import 'lib3ImportsLib1.ftl' as lib3>${lib1Cnt} ${.main.lib1???c} ${.globals.lib1???c}, "
         + "<#import 'lib1.ftl' as lib1>${lib1Cnt} ${.main.lib1???c} ${.globals.lib1???c}";

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
index 646b1dd..08fcee2 100644
--- a/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
+++ b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
@@ -22,13 +22,12 @@ import java.util.Arrays;
 import java.util.Iterator;
 
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.test.TemplateTest;
 import org.junit.Test;
 
 public class IteratorIssuesTest extends TemplateTest {
 
-    private static final DefaultObjectWrapper OW = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+    private static final DefaultObjectWrapper OW = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
 
     private static final String FTL_HAS_CONTENT_AND_LIST
             = "<#if it?hasContent><#list it as i>${i}</#list><#else>empty</#if>";

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/ListErrorsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ListErrorsTest.java b/src/test/java/org/apache/freemarker/core/ListErrorsTest.java
index b798a35..05bac4f 100644
--- a/src/test/java/org/apache/freemarker/core/ListErrorsTest.java
+++ b/src/test/java/org/apache/freemarker/core/ListErrorsTest.java
@@ -121,7 +121,7 @@ public class ListErrorsTest extends TemplateTest {
     @Test
     public void testNonEx2NonStringKey() throws IOException, TemplateException {
         addToDataModel("m", new Listables.NonEx2MapAdapter(ImmutableMap.of("k1", "v1", 2, "v2"),
-                new DefaultObjectWrapper(Configuration.VERSION_3_0_0)));
+                new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build()));
         assertOutput("<#list m?keys as k>${k};</#list>", "k1;2;");
         assertErrorContains("<#list m as k, v></#list>",
                 "string", "number", ".TemplateHashModelEx2");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java b/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
index 5404af8..1b36efe 100644
--- a/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
@@ -49,7 +49,6 @@ import org.apache.freemarker.core.templateresolver.TemplateLoadingResult;
 import org.apache.freemarker.core.templateresolver.TemplateLoadingSource;
 import org.apache.freemarker.core.templateresolver.impl.MruCacheStorage;
 import org.apache.freemarker.core.userpkg.PublicWithMixedConstructors;
-import org.apache.freemarker.core.util.WriteProtectable;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -236,31 +235,6 @@ public class ObjectBuilderSettingsTest {
             assertNotSame(TestBean5.INSTANCE, res);
         }
     }
-    
-    @Test
-    public void writeProtectionTest() throws Exception {
-        {
-            TestBean3 res = (TestBean3) _ObjectBuilderSettingEvaluator.eval(
-                    "org.apache.freemarker.core.ObjectBuilderSettingsTest$TestBean3(x = 1)",
-                    Object.class, false, _SettingEvaluationEnvironment.getCurrent());
-            assertEquals(1, res.x);
-            assertTrue(res.isWriteProtected());
-            try {
-                res.setX(2);
-                fail();
-            } catch (IllegalStateException e) {
-                // expected
-            }
-        }
-        
-        {
-            TestBean3 res = (TestBean3) _ObjectBuilderSettingEvaluator.eval(
-                    "org.apache.freemarker.core.ObjectBuilderSettingsTest$TestBean3",
-                    Object.class, false, _SettingEvaluationEnvironment.getCurrent());
-            assertEquals(0, res.x);
-            assertTrue(res.isWriteProtected()); // Still uses a builder
-        }
-    }
 
     @Test
     public void stringLiteralsTest() throws Exception {
@@ -398,7 +372,6 @@ public class ObjectBuilderSettingsTest {
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyTemplateLoader()");
             cfg.setSettings(props);
             assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
-            assertTrue(((WriteProtectable) cfg.getObjectWrapper()).isWriteProtected());
             assertEquals(
                     Configuration.VERSION_3_0_0, ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
             assertEquals(DummyArithmeticEngine.class, cfg.getArithmeticEngine().getClass());
@@ -422,7 +395,6 @@ public class ObjectBuilderSettingsTest {
                     "allows_nothing");
             cfg.setSettings(props);
             assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
-            assertTrue(((WriteProtectable) cfg.getObjectWrapper()).isWriteProtected());
             assertEquals(1, ((DummyArithmeticEngine) cfg.getArithmeticEngine()).getX());
             assertEquals(1, ((DummyTemplateExceptionHandler) cfg.getTemplateExceptionHandler()).getX());
             assertEquals(Configuration.VERSION_3_0_0,
@@ -441,7 +413,6 @@ public class ObjectBuilderSettingsTest {
             assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
             assertSame(BigDecimalArithmeticEngine.INSTANCE, cfg.getArithmeticEngine());
             assertSame(TemplateExceptionHandler.RETHROW_HANDLER, cfg.getTemplateExceptionHandler());
-            assertTrue(((WriteProtectable) cfg.getObjectWrapper()).isWriteProtected());
             assertEquals(Configuration.VERSION_3_0_0,
                     ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
         }
@@ -451,7 +422,6 @@ public class ObjectBuilderSettingsTest {
             props.setProperty(Configurable.OBJECT_WRAPPER_KEY, "DefaultObjectWrapper(3.0.0)");
             cfg.setSettings(props);
             assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
-            assertTrue(((WriteProtectable) cfg.getObjectWrapper()).isWriteProtected());
             assertEquals(
                     Configuration.VERSION_3_0_0,
                     ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
@@ -1120,28 +1090,15 @@ public class ObjectBuilderSettingsTest {
         
     }
 
-    public static class TestBean3 implements WriteProtectable {
-        
-        private boolean writeProtected;
+    public static class TestBean3 {
         
         private int x;
 
-        @Override
-        public void writeProtect() {
-            writeProtected = true;
-        }
-
-        @Override
-        public boolean isWriteProtected() {
-            return writeProtected;
-        }
-
         public int getX() {
             return x;
         }
 
         public void setX(int x) {
-            if (writeProtected) throw new IllegalStateException();
             this.x = x;
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java b/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java
index d76a31e..702a254 100644
--- a/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java
+++ b/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java
@@ -46,7 +46,7 @@ public class RestrictedObjectWrapperTest {
     @Test
     public void testBasics() throws TemplateModelException {
         PostConstruct.class.toString();
-        RestrictedObjectWrapper ow = new RestrictedObjectWrapper(Configuration.VERSION_3_0_0);
+        RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         testCustomizationCommonPart(ow);
         assertTrue(ow.wrap(Collections.emptyMap()) instanceof DefaultMapAdapter);
         assertTrue(ow.wrap(Collections.emptyList()) instanceof DefaultListAdapter);
@@ -56,8 +56,6 @@ public class RestrictedObjectWrapperTest {
 
     @SuppressWarnings("boxing")
     private void testCustomizationCommonPart(RestrictedObjectWrapper ow) throws TemplateModelException {
-        assertFalse(ow.isWriteProtected());
-        
         assertTrue(ow.wrap("x") instanceof SimpleScalar);
         assertTrue(ow.wrap(1.5) instanceof SimpleNumber);
         assertTrue(ow.wrap(new Date()) instanceof SimpleDate);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java b/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java
new file mode 100644
index 0000000..43ff3bf
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateCollectionModelEx;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class RestrictedObjetWrapperTest {
+    
+    @Test
+    public void testDoesNotAllowAPIBuiltin() throws TemplateModelException {
+        RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        
+        TemplateModelWithAPISupport map = (TemplateModelWithAPISupport) sow.wrap(new HashMap());
+        try {
+            map.getAPI();
+            fail();
+        } catch (TemplateException e) {
+            assertThat(e.getMessage(), containsString("?api"));
+        }
+    }
+
+    @SuppressWarnings("boxing")
+    @Test
+    public void testCanWrapBasicTypes() throws TemplateModelException {
+        RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        assertTrue(sow.wrap("s") instanceof TemplateScalarModel);
+        assertTrue(sow.wrap(1) instanceof TemplateNumberModel);
+        assertTrue(sow.wrap(true) instanceof TemplateBooleanModel);
+        assertTrue(sow.wrap(new Date()) instanceof TemplateDateModel);
+        assertTrue(sow.wrap(new ArrayList()) instanceof TemplateSequenceModel);
+        assertTrue(sow.wrap(new String[0]) instanceof TemplateSequenceModel);
+        assertTrue(sow.wrap(new ArrayList().iterator()) instanceof TemplateCollectionModel);
+        assertTrue(sow.wrap(new HashSet()) instanceof TemplateCollectionModelEx);
+        assertTrue(sow.wrap(new HashMap()) instanceof TemplateHashModelEx2);
+        assertNull(sow.wrap(null));
+    }
+    
+    @Test
+    public void testWontWrapDOM() throws SAXException, IOException, ParserConfigurationException,
+            TemplateModelException {
+        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        InputSource is = new InputSource();
+        is.setCharacterStream(new StringReader("<doc><sub a='1' /></doc>"));
+        Document doc = db.parse(is);
+        
+        RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        try {
+            sow.wrap(doc);
+            fail();
+        } catch (TemplateModelException e) {
+            assertThat(e.getMessage(), containsString("won't wrap"));
+        }
+    }
+    
+    @Test
+    public void testWontWrapGenericObjects() {
+        RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        try {
+            sow.wrap(new File("/x"));
+            fail();
+        } catch (TemplateModelException e) {
+            assertThat(e.getMessage(), containsString("won't wrap"));
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/SimpleObjetWrapperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/SimpleObjetWrapperTest.java b/src/test/java/org/apache/freemarker/core/SimpleObjetWrapperTest.java
deleted file mode 100644
index 040695d..0000000
--- a/src/test/java/org/apache/freemarker/core/SimpleObjetWrapperTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateCollectionModelEx;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx2;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
-import org.junit.Test;
-import org.w3c.dom.Document;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-public class SimpleObjetWrapperTest {
-    
-    @Test
-    public void testDoesNotAllowAPIBuiltin() throws TemplateModelException {
-        RestrictedObjectWrapper sow = new RestrictedObjectWrapper(Configuration.VERSION_3_0_0);
-        
-        TemplateModelWithAPISupport map = (TemplateModelWithAPISupport) sow.wrap(new HashMap());
-        try {
-            map.getAPI();
-            fail();
-        } catch (TemplateException e) {
-            assertThat(e.getMessage(), containsString("?api"));
-        }
-    }
-
-    @SuppressWarnings("boxing")
-    @Test
-    public void testCanWrapBasicTypes() throws TemplateModelException {
-        RestrictedObjectWrapper sow = new RestrictedObjectWrapper(Configuration.VERSION_3_0_0);
-        assertTrue(sow.wrap("s") instanceof TemplateScalarModel);
-        assertTrue(sow.wrap(1) instanceof TemplateNumberModel);
-        assertTrue(sow.wrap(true) instanceof TemplateBooleanModel);
-        assertTrue(sow.wrap(new Date()) instanceof TemplateDateModel);
-        assertTrue(sow.wrap(new ArrayList()) instanceof TemplateSequenceModel);
-        assertTrue(sow.wrap(new String[0]) instanceof TemplateSequenceModel);
-        assertTrue(sow.wrap(new ArrayList().iterator()) instanceof TemplateCollectionModel);
-        assertTrue(sow.wrap(new HashSet()) instanceof TemplateCollectionModelEx);
-        assertTrue(sow.wrap(new HashMap()) instanceof TemplateHashModelEx2);
-        assertNull(sow.wrap(null));
-    }
-    
-    @Test
-    public void testWontWrapDOM() throws SAXException, IOException, ParserConfigurationException,
-            TemplateModelException {
-        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-        InputSource is = new InputSource();
-        is.setCharacterStream(new StringReader("<doc><sub a='1' /></doc>"));
-        Document doc = db.parse(is);
-        
-        RestrictedObjectWrapper sow = new RestrictedObjectWrapper(Configuration.VERSION_3_0_0);
-        try {
-            sow.wrap(doc);
-            fail();
-        } catch (TemplateModelException e) {
-            assertThat(e.getMessage(), containsString("won't wrap"));
-        }
-    }
-    
-    @Test
-    public void testWontWrapGenericObjects() {
-        RestrictedObjectWrapper sow = new RestrictedObjectWrapper(Configuration.VERSION_3_0_0);
-        try {
-            sow.wrap(new File("/x"));
-            fail();
-        } catch (TemplateModelException e) {
-            assertThat(e.getMessage(), containsString("won't wrap"));
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java b/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
index 551912f..07b39e4 100644
--- a/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
+++ b/src/test/java/org/apache/freemarker/core/TagSyntaxVariationsTest.java
@@ -160,7 +160,7 @@ public class TagSyntaxVariationsTest extends TestCase {
             t = new Template("string", new StringReader(template), cfg);
         } catch (ParseException e) {
             if (expected != null) {
-                fail("Couldn't create Template from "
+                fail("Couldn't invoke Template from "
                         + _StringUtil.jQuote(template) + ": " + e);
             } else {
                 return;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
index bf3587e..7943a2d 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
@@ -172,7 +172,7 @@ public class TemplateConfigurationTest {
         SETTING_ASSIGNMENTS.put("logTemplateExceptions", true);
         SETTING_ASSIGNMENTS.put("newBuiltinClassResolver", TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
         SETTING_ASSIGNMENTS.put("numberFormat", "0.0000");
-        SETTING_ASSIGNMENTS.put("objectWrapper", new RestrictedObjectWrapper(ICI));
+        SETTING_ASSIGNMENTS.put("objectWrapper", new RestrictedObjectWrapper.Builder(ICI).build());
         SETTING_ASSIGNMENTS.put("outputEncoding", "utf-16");
         SETTING_ASSIGNMENTS.put("showErrorTips", false);
         SETTING_ASSIGNMENTS.put("templateExceptionHandler", TemplateExceptionHandler.IGNORE_HANDLER);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/AbstractParallelIntrospectionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/AbstractParallelIntrospectionTest.java b/src/test/java/org/apache/freemarker/core/model/impl/AbstractParallelIntrospectionTest.java
index 250da24..bdb9a56 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/AbstractParallelIntrospectionTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/AbstractParallelIntrospectionTest.java
@@ -35,7 +35,8 @@ public abstract class AbstractParallelIntrospectionTest extends TestCase {
     private static final int ITERATIONS = 20000;
     private static final double CACHE_CLEARING_CHANCE = 0.01;
     
-    private DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+    private DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+            .usePrivateCaches(true).build();
     
     public AbstractParallelIntrospectionTest(String name) {
         super(name);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperDesc.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperDesc.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperDesc.java
index 250c734..0ed6e7c 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperDesc.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperDesc.java
@@ -21,10 +21,11 @@ package org.apache.freemarker.core.model.impl;
 
 import org.apache.freemarker.core.Configuration;
 
-public class DefaultObjectWrapperDesc extends DefaultObjectWrapperWithSortedMethods {
+public class DefaultObjectWrapperDesc extends DefaultObjectWrapper {
 
     public DefaultObjectWrapperDesc() {
-        super(Configuration.VERSION_3_0_0, true);
+        super(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                .methodSorter(new AlphabeticalMethodSorter(true)), true);
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperInc.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperInc.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperInc.java
index bf25611..eb2cda0 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperInc.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperInc.java
@@ -21,10 +21,11 @@ package org.apache.freemarker.core.model.impl;
 
 import org.apache.freemarker.core.Configuration;
 
-public class DefaultObjectWrapperInc extends DefaultObjectWrapperWithSortedMethods {
+public class DefaultObjectWrapperInc extends DefaultObjectWrapper {
 
     public DefaultObjectWrapperInc() {
-        super(Configuration.VERSION_3_0_0, false);
+        super(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                .methodSorter(new AlphabeticalMethodSorter(false)), true);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperModelFactoryRegistrationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperModelFactoryRegistrationTest.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperModelFactoryRegistrationTest.java
index 270a39e..5c2f653 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperModelFactoryRegistrationTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperModelFactoryRegistrationTest.java
@@ -30,41 +30,10 @@ public class DefaultObjectWrapperModelFactoryRegistrationTest {
 
     @Test
     public void introspectionSettingChanges() {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).usePrivateCaches(true)
+                .build();
         ClassIntrospector ci1 = ow.getClassIntrospector();
         checkRegisteredModelFactories(ci1, ow.getStaticModels(), ow.getEnumModels());
-
-        ow.setExposeFields(true);
-        ClassIntrospector ci2 = ow.getClassIntrospector();
-        assertNotSame(ci1, ci2);
-        checkRegisteredModelFactories(ci1);
-        checkRegisteredModelFactories(ci2, ow.getStaticModels(), ow.getEnumModels());
-
-        ow.setExposureLevel(DefaultObjectWrapper.EXPOSE_ALL);
-        ClassIntrospector ci3 = ow.getClassIntrospector();
-        assertNotSame(ci2, ci3);
-        checkRegisteredModelFactories(ci2);
-        checkRegisteredModelFactories(ci3, ow.getStaticModels(), ow.getEnumModels());
-
-        MethodAppearanceFineTuner maf = new MethodAppearanceFineTuner() {
-            @Override
-            public void process(DecisionInput in, Decision out) {
-                // nop
-            }
-        };
-        ow.setMethodAppearanceFineTuner(maf);
-        ClassIntrospector ci4 = ow.getClassIntrospector();
-        assertNotSame(ci3, ci4);
-        checkRegisteredModelFactories(ci3);
-        checkRegisteredModelFactories(ci4, ow.getStaticModels(), ow.getEnumModels());
-
-        ow.setExposeFields(true);
-        assertSame(ci4, ow.getClassIntrospector());
-        ow.setExposureLevel(DefaultObjectWrapper.EXPOSE_ALL);
-        assertSame(ci4, ow.getClassIntrospector());
-        ow.setMethodAppearanceFineTuner(maf);
-        assertSame(ci4, ow.getClassIntrospector());
-        checkRegisteredModelFactories(ci4, ow.getStaticModels(), ow.getEnumModels());
     }
 
     private void checkRegisteredModelFactories(ClassIntrospector ci, Object... expected) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperReadOnlyTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperReadOnlyTest.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperReadOnlyTest.java
deleted file mode 100644
index 72e9bd8..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperReadOnlyTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import java.beans.BeanInfo;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.util.WriteProtectable;
-import org.apache.freemarker.core.util._ClassUtil;
-
-import junit.framework.TestCase;
-
-/**
- * Tests if all JavaBean properties of {@link DefaultObjectWrapper} are locked by
- * {@link WriteProtectable#writeProtect()}.
- */
-public class DefaultObjectWrapperReadOnlyTest extends TestCase {
-
-    private static final String EXPECTED_MESSAGE_PART = "write protected";
-
-    public DefaultObjectWrapperReadOnlyTest(String name) {
-        super(name);
-    }
-    
-    public void testDefaultObjectWrapper() throws Exception {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
-        ow.writeProtect();
-        checkAllPropertiesReadOnly(ow);
-    }
-    
-    private void checkAllPropertiesReadOnly(Object o) throws Exception {
-        BeanInfo bi = Introspector.getBeanInfo(o.getClass());
-        for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
-            Method setter = pd.getWriteMethod();
-            if (setter != null) {
-                Class t = pd.getPropertyType();
-                
-                Object val;
-                if (_ClassUtil.isNumerical(t)) {
-                    val = Byte.valueOf((byte) 1);
-                } else if (t == boolean.class) {
-                    val = Boolean.TRUE;
-                } else if (t == char.class) {
-                    val = Character.valueOf('c');
-                } else if (t.isAssignableFrom(String.class)) {
-                    val = "s";
-                } else {
-                    val = null;
-                }
-                try {
-                    setter.invoke(o, val);
-                    fail("This setter should have failed as the property should be read-only now: " + setter);
-                } catch (InvocationTargetException e) {
-                    Throwable target = e.getTargetException();
-                    if (!(target instanceof IllegalStateException
-                            && target.getMessage() != null
-                            && target.getMessage().contains(EXPECTED_MESSAGE_PART))) {
-                        fail("Expected IllegalStateException with message containing \"" + EXPECTED_MESSAGE_PART
-                                + "\", got this instead: " + target);
-                    }
-                }
-            }
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperSingletonsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperSingletonsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperSingletonsTest.java
index cf84333..355aa12 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperSingletonsTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperSingletonsTest.java
@@ -46,23 +46,23 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
     
     @Override
     protected void setUp() throws Exception {
-        DefaultObjectWrapperBuilder.clearInstanceCache();
+        DefaultObjectWrapper.Builder.clearInstanceCache();
     }
 
     public void testBuilderEqualsAndHash() throws Exception {
-        assertEquals(Configuration.VERSION_3_0_0, new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).getIncompatibleImprovements());
+        assertEquals(Configuration.VERSION_3_0_0, new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).getIncompatibleImprovements());
         try {
-            new DefaultObjectWrapperBuilder(TestUtil.getClosestFutureVersion());
+            new DefaultObjectWrapper.Builder(TestUtil.getClosestFutureVersion());
             fail("Maybe you need to update this test for the new FreeMarker version");
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("upgrade"));
         }
 
-        DefaultObjectWrapperBuilder builder1;
-        DefaultObjectWrapperBuilder builder2;
+        DefaultObjectWrapper.Builder builder1;
+        DefaultObjectWrapper.Builder builder2;
         
-        builder1 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
-        builder2 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+        builder1 = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
+        builder2 = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
         assertEquals(builder1, builder2);
         
         builder1.setExposeFields(true);
@@ -134,7 +134,6 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
             assertEquals(1, getDefaultObjectWrapperInstanceCacheSize());
             assertSame(ow.getClass(), DefaultObjectWrapper.class);
             assertEquals(Configuration.VERSION_3_0_0, ow.getIncompatibleImprovements());
-            assertTrue(ow.isWriteProtected());
             assertFalse(ow.isStrict());
             assertTrue(ow.getUseModelCache());
             assertEquals(TemplateDateModel.UNKNOWN, ow.getDefaultDateType());
@@ -142,14 +141,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
             assertTrue(ow.isClassIntrospectionCacheRestricted());
             assertNull(ow.getMethodAppearanceFineTuner());
             assertNull(ow.getMethodSorter());
-            
-            try {
-                ow.setExposeFields(true);  // can't modify the settings of a (potential) singleton
-                fail();
-            } catch (IllegalStateException e) {
-                assertThat(e.getMessage(), containsString("modify"));
-            }
-            
+
             assertSame(ow, getDefaultObjectWrapperWithSetting(Configuration.VERSION_3_0_0, true));
             assertEquals(1, getDefaultObjectWrapperInstanceCacheSize());
             
@@ -161,7 +153,6 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
             assertEquals(2, getDefaultObjectWrapperInstanceCacheSize());
             assertSame(ow.getClass(), DefaultObjectWrapper.class);
             assertEquals(Configuration.VERSION_3_0_0, ow.getIncompatibleImprovements());
-            assertTrue(ow.isWriteProtected());
             assertFalse(ow.getUseModelCache());
 
             assertSame(ow, getDefaultObjectWrapperWithSetting(Configuration.VERSION_3_0_0, false));
@@ -170,7 +161,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
         
         {
-            DefaultObjectWrapperBuilder factory = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper.Builder factory = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             factory.setExposureLevel(DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY);
             DefaultObjectWrapper ow1 = factory.build();
             DefaultObjectWrapper ow2 = factory.build();
@@ -179,7 +170,6 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
             
             assertSame(ow1.getClass(), DefaultObjectWrapper.class);
             assertEquals(Configuration.VERSION_3_0_0, ow1.getIncompatibleImprovements());
-            assertTrue(ow1.isWriteProtected());
             assertEquals(DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY, ow1.getExposureLevel());
             assertFalse(ow1.isStrict());
             assertEquals(TemplateDateModel.UNKNOWN, ow1.getDefaultDateType());
@@ -189,7 +179,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
         
         {
-            DefaultObjectWrapperBuilder factory = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper.Builder factory = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             factory.setExposeFields(true);
             DefaultObjectWrapper ow1 = factory.build();
             DefaultObjectWrapper ow2 = factory.build();
@@ -198,17 +188,16 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
             
             assertSame(ow1.getClass(), DefaultObjectWrapper.class);
             assertEquals(Configuration.VERSION_3_0_0, ow1.getIncompatibleImprovements());
-            assertTrue(ow1.isWriteProtected());
             assertTrue(ow1.isExposeFields());
             
             hardReferences.add(ow1);
         }
         
         {
-            DefaultObjectWrapperBuilder factory = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper.Builder factory = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             factory.setStrict(true);
             factory.setDefaultDateType(TemplateDateModel.DATETIME);
-            factory.setOuterIdentity(new RestrictedObjectWrapper(Configuration.VERSION_3_0_0));
+            factory.setOuterIdentity(new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
             DefaultObjectWrapper ow = factory.build();
             assertEquals(5, getDefaultObjectWrapperInstanceCacheSize());
             assertTrue(ow.isStrict());
@@ -220,7 +209,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         
         // Effect of reference and cache clearings:
         {
-            DefaultObjectWrapper bw1 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+            DefaultObjectWrapper bw1 = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
             assertEquals(5, getDefaultObjectWrapperInstanceCacheSize());
             assertEquals(5, getDefaultObjectWrapperNonClearedInstanceCacheSize());
             
@@ -228,23 +217,23 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
             assertEquals(5, getDefaultObjectWrapperInstanceCacheSize());
             assertEquals(0, getDefaultObjectWrapperNonClearedInstanceCacheSize());
             
-            DefaultObjectWrapper bw2 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+            DefaultObjectWrapper bw2 = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
             assertNotSame(bw1, bw2);
             assertEquals(5, getDefaultObjectWrapperInstanceCacheSize());
             assertEquals(1, getDefaultObjectWrapperNonClearedInstanceCacheSize());
             
-            assertSame(bw2, new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build());
+            assertSame(bw2, new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
             assertEquals(1, getDefaultObjectWrapperNonClearedInstanceCacheSize());
             
             clearDefaultObjectWrapperInstanceCacheReferences(true);
-            DefaultObjectWrapper bw3 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+            DefaultObjectWrapper bw3 = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
             assertNotSame(bw2, bw3);
             assertEquals(1, getDefaultObjectWrapperInstanceCacheSize());
             assertEquals(1, getDefaultObjectWrapperNonClearedInstanceCacheSize());
         }
 
         {
-            DefaultObjectWrapperBuilder factory = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper.Builder factory = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             factory.setUseModelCache(true);
             DefaultObjectWrapper ow = factory.build();
             assertTrue(ow.getUseModelCache());
@@ -257,7 +246,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
     }
     
     private DefaultObjectWrapper getDefaultObjectWrapperWithSetting(Version ici, boolean useModelCache) {
-        DefaultObjectWrapperBuilder f = new DefaultObjectWrapperBuilder(ici);
+        DefaultObjectWrapper.Builder f = new DefaultObjectWrapper.Builder(ici);
         f.setUseModelCache(useModelCache);
         return f.build();
     }
@@ -267,7 +256,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         
         assertEquals(0, getDefaultObjectWrapperInstanceCacheSize());
         
-        DefaultObjectWrapper bw1 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        DefaultObjectWrapper bw1 = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
         assertEquals(1, getDefaultObjectWrapperInstanceCacheSize());
         hardReferences.add(bw1);
         
@@ -278,25 +267,25 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         DefaultObjectWrapper bw2;
         Thread.currentThread().setContextClassLoader(newTCCL);
         try {
-            bw2 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+            bw2 = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
             assertEquals(2, getDefaultObjectWrapperInstanceCacheSize());
             hardReferences.add(bw2);
             
             assertNotSame(bw1, bw2);
-            assertSame(bw2, new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build());
+            assertSame(bw2, new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
         } finally {
             Thread.currentThread().setContextClassLoader(oldTCCL);
         }
         
-        assertSame(bw1, new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build());
+        assertSame(bw1, new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
         assertEquals(2, getDefaultObjectWrapperInstanceCacheSize());
 
         DefaultObjectWrapper bw3;
         Thread.currentThread().setContextClassLoader(newTCCL);
         try {
-            assertSame(bw2, new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build());
+            assertSame(bw2, new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
             
-            DefaultObjectWrapperBuilder bwb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper.Builder bwb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             bwb.setExposeFields(true);
             bw3 = bwb.build();
             assertEquals(3, getDefaultObjectWrapperInstanceCacheSize());
@@ -306,7 +295,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
         
         {
-            DefaultObjectWrapperBuilder bwb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper.Builder bwb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             bwb.setExposeFields(true);
             DefaultObjectWrapper bw4 = bwb.build();
             assertEquals(4, getDefaultObjectWrapperInstanceCacheSize());
@@ -318,19 +307,20 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
     }
     
     public void testClassInrospectorCache() throws TemplateModelException {
-        assertFalse(new DefaultObjectWrapper(Configuration.VERSION_3_0_0).isClassIntrospectionCacheRestricted());
-        assertTrue(new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0)
+        assertFalse(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                .usePrivateCaches(true).build().isClassIntrospectionCacheRestricted());
+        assertTrue(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
                 .build().isClassIntrospectionCacheRestricted());
         
-        ClassIntrospectorBuilder.clearInstanceCache();
-        DefaultObjectWrapperBuilder.clearInstanceCache();
+        ClassIntrospector.Builder.clearInstanceCache();
+        DefaultObjectWrapper.Builder.clearInstanceCache();
         checkClassIntrospectorCacheSize(0);
         
         List<DefaultObjectWrapper> hardReferences = new LinkedList<>();
-        DefaultObjectWrapperBuilder builder;
+        DefaultObjectWrapper.Builder builder;
 
         {
-            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             
             DefaultObjectWrapper bw1 = builder.build();
             checkClassIntrospectorCacheSize(1);
@@ -355,7 +345,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
         
         {
-            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             builder.setExposeFields(true);
             DefaultObjectWrapper ow = builder.build();
             checkClassIntrospectorCacheSize(2);
@@ -434,7 +424,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
         
         {
-            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             builder.setUseModelCache(true);
             builder.setExposeFields(false);
             builder.setExposureLevel(DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY);
@@ -463,7 +453,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
 
         // The ClassInrospector cache couldn't become cleared in reality otherwise:
-        DefaultObjectWrapperBuilder.clearInstanceCache();
+        DefaultObjectWrapper.Builder.clearInstanceCache();
 
         clearClassIntrospectorInstanceCacheReferences(false);
         checkClassIntrospectorCacheSize(8);
@@ -496,7 +486,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
         
         {
-            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             DefaultObjectWrapper ow = builder.build();
             checkClassIntrospectorCacheSize(8);
             assertEquals(2, getClassIntrospectorNonClearedInstanceCacheSize());
@@ -514,7 +504,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         assertEquals(0, getClassIntrospectorNonClearedInstanceCacheSize());
         
         {
-            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             builder.setExposeFields(true);
             DefaultObjectWrapper ow = builder.build();
             checkClassIntrospectorCacheSize(1);
@@ -528,7 +518,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
         
         {
-            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             builder.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
                 @Override
                 public void process(DecisionInput in, Decision out) {
@@ -549,7 +539,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
         }
 
         {
-            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
             builder.setMethodAppearanceFineTuner(
                     GetlessMethodsAsPropertyGettersRule.INSTANCE);  // doesn't spoils sharing
 
@@ -611,14 +601,14 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
     }
     
     static int getClassIntrospectorInstanceCacheSize() {
-        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
+        Map instanceCache = ClassIntrospector.Builder.getInstanceCache();
         synchronized (instanceCache) {
             return instanceCache.size();
         }
     }
 
     static int getClassIntrospectorNonClearedInstanceCacheSize() {
-        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
+        Map instanceCache = ClassIntrospector.Builder.getInstanceCache();
         synchronized (instanceCache) {
             int cnt = 0;
             for (Iterator it = instanceCache.values().iterator(); it.hasNext(); ) {
@@ -629,7 +619,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
     }
     
     static void clearClassIntrospectorInstanceCacheReferences(boolean enqueue) {
-        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
+        Map instanceCache = ClassIntrospector.Builder.getInstanceCache();
         synchronized (instanceCache) {
             for (Iterator it = instanceCache.values().iterator(); it.hasNext(); ) {
                 Reference ref = ((Reference) it.next());
@@ -642,7 +632,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
     }
 
     static int getDefaultObjectWrapperInstanceCacheSize() {
-        Map instanceCache = DefaultObjectWrapperBuilder.getInstanceCache();
+        Map instanceCache = DefaultObjectWrapper.Builder.getInstanceCache();
         synchronized (instanceCache) {
             int size = 0; 
             for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
@@ -653,7 +643,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
     }
 
     static int getDefaultObjectWrapperNonClearedInstanceCacheSize() {
-        Map instanceCache = DefaultObjectWrapperBuilder.getInstanceCache();
+        Map instanceCache = DefaultObjectWrapper.Builder.getInstanceCache();
         synchronized (instanceCache) {
             int cnt = 0;
             for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
@@ -667,7 +657,7 @@ public class DefaultObjectWrapperSingletonsTest extends TestCase {
     }
     
     static void clearDefaultObjectWrapperInstanceCacheReferences(boolean enqueue) {
-        Map instanceCache = DefaultObjectWrapperBuilder.getInstanceCache();
+        Map instanceCache = DefaultObjectWrapper.Builder.getInstanceCache();
         synchronized (instanceCache) {
             for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
                 Map tcclScope = (Map) it1.next();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dceec32e/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
index 38076a1..93d8a31 100644
--- a/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
@@ -78,7 +78,7 @@ import com.google.common.collect.ImmutableMap;
 
 public class DefaultObjectWrapperTest {
 
-    private final static DefaultObjectWrapper OW = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0)
+    private final static DefaultObjectWrapper OW = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
             .build();
 
     // This will make sense if we will have multipe incompatibleImprovement versions.
@@ -98,7 +98,7 @@ public class DefaultObjectWrapperTest {
             final Version normalizedVersion = DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(version);
             actual.add(normalizedVersion);
 
-            final DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(version);
+            final DefaultObjectWrapper.Builder builder = new DefaultObjectWrapper.Builder(version);
             assertEquals(normalizedVersion, builder.getIncompatibleImprovements());
             assertEquals(normalizedVersion, builder.build().getIncompatibleImprovements());
             
@@ -127,7 +127,7 @@ public class DefaultObjectWrapperTest {
             // expected
         }
         try {
-            new DefaultObjectWrapperBuilder(futureVersion);
+            new DefaultObjectWrapper.Builder(futureVersion);
             fail();
         } catch (IllegalArgumentException e) {
             // expected
@@ -137,17 +137,16 @@ public class DefaultObjectWrapperTest {
     @SuppressWarnings("boxing")
     @Test
     public void testBuilder() throws Exception {
-        DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+        DefaultObjectWrapper.Builder builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
         DefaultObjectWrapper ow = builder.build();
         assertSame(ow, builder.build());
         assertSame(ow.getClass(), DefaultObjectWrapper.class);
         assertEquals(Configuration.VERSION_3_0_0, ow.getIncompatibleImprovements());
-        assertTrue(ow.isWriteProtected());
     }
 
     @Test
     public void testWrappedTypes() throws Exception {
-        DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+        DefaultObjectWrapper.Builder builder = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
         DefaultObjectWrapper ow = builder.build();
 
         assertThat(ow.wrap(new HashMap()), instanceOf(DefaultMapAdapter.class));
@@ -162,18 +161,13 @@ public class DefaultObjectWrapperTest {
     @Test
     public void testConstructors() throws Exception {
         {
-            DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
-            assertEquals(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS, ow.getIncompatibleImprovements());
-        }
-        
-        {
-            DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                    .usePrivateCaches(true).build();
             assertEquals(Configuration.VERSION_3_0_0, ow.getIncompatibleImprovements());
-            assertFalse(ow.isWriteProtected());
         }
         
         try {
-            new DefaultObjectWrapper(new Version(99, 9, 9));
+            new DefaultObjectWrapper.Builder(new Version(99, 9, 9)).build();
             fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("version"));
@@ -185,8 +179,7 @@ public class DefaultObjectWrapperTest {
     public void testCustomization() throws TemplateModelException {
         CustomizedDefaultObjectWrapper ow = new CustomizedDefaultObjectWrapper(Configuration.VERSION_3_0_0);
         assertEquals(Configuration.VERSION_3_0_0, ow.getIncompatibleImprovements());
-        assertFalse(ow.isWriteProtected());
-        
+
         TemplateSequenceModel seq = (TemplateSequenceModel) ow.wrap(new Tupple(11, 22));
         assertEquals(2, seq.size());
         assertEquals(11, ow.unwrap(seq.get(0)));
@@ -225,7 +218,7 @@ public class DefaultObjectWrapperTest {
     @SuppressWarnings("boxing")
     @Test
     public void testCompositeValueWrapping() throws TemplateModelException, ClassNotFoundException {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
 
         final Map hashMap = new HashMap();
         inintTestMap(hashMap);
@@ -705,20 +698,37 @@ public class DefaultObjectWrapperTest {
 
     @Test
     public void testExposureLevel() throws Exception {
-        final DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
-
-        TemplateHashModel tm = (TemplateHashModel) ow.wrap(new TestBean());
-        assertNotNull(tm.get("hashCode"));
-        assertNotNull(tm.get("class"));
-        ow.setExposureLevel(DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY);
-        assertNull(tm.get("hashCode"));
-        assertNotNull(tm.get("class"));
-        ow.setExposureLevel(DefaultObjectWrapper.EXPOSE_NOTHING);
-        assertNull(tm.get("hashCode"));
-        assertNull(tm.get("class"));
-        ow.setExposureLevel(DefaultObjectWrapper.EXPOSE_ALL);
-        assertNotNull(tm.get("hashCode"));
-        assertNotNull(tm.get("class"));
+        TestBean bean = new TestBean();
+
+        {
+            TemplateHashModel tm = wrapWithExposureLevel(bean, DefaultObjectWrapper.EXPOSE_SAFE);
+            assertNotNull(tm.get("hashCode"));
+            assertNotNull(tm.get("class"));
+        }
+
+        {
+            TemplateHashModel tm = wrapWithExposureLevel(bean, DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY);
+            assertNull(tm.get("hashCode"));
+            assertNotNull(tm.get("class"));
+        }
+
+        {
+            TemplateHashModel tm = wrapWithExposureLevel(bean, DefaultObjectWrapper.EXPOSE_NOTHING);
+            assertNull(tm.get("hashCode"));
+            assertNull(tm.get("class"));
+        }
+
+        {
+            TemplateHashModel tm = wrapWithExposureLevel(bean, DefaultObjectWrapper.EXPOSE_ALL);
+            assertNotNull(tm.get("hashCode"));
+            assertNotNull(tm.get("class"));
+        }
+    }
+
+    private TemplateHashModel wrapWithExposureLevel(Object bean, int exposureLevel) throws TemplateModelException {
+        return (TemplateHashModel) new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                .exposureLevel(exposureLevel).build()
+                .wrap(bean);
     }
 
     private void assertSizeThroughAPIModel(int expectedSize, TemplateModel normalModel) throws TemplateModelException {
@@ -840,7 +850,7 @@ public class DefaultObjectWrapperTest {
     private static class CustomizedDefaultObjectWrapper extends DefaultObjectWrapper {
 
         private CustomizedDefaultObjectWrapper(Version incompatibleImprovements) {
-            super(incompatibleImprovements);
+            super(new DefaultObjectWrapper.Builder(incompatibleImprovements), true);
         }
         
         @Override