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:52 UTC

[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

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