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/02/28 22:57:35 UTC

[01/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 3187a094e -> 051a08223


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/expected/beans.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/expected/beans.txt b/src/test/resources/org/apache/freemarker/test/templatesuite/expected/beans.txt
deleted file mode 100644
index ab41e26..0000000
--- a/src/test/resources/org/apache/freemarker/test/templatesuite/expected/beans.txt
+++ /dev/null
@@ -1,56 +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.
- */
-array-0
-array-1
-2
-array-0
-array-1
-list-0
-list-1
-list-2
-3
-not empty
-list-0
-value
-objValue
-foo-value
-hasfoo
-nobaz
-bar-value-0
-bar-value-0
-foo-value
-overloaded-int-1
-overloaded-String-String
-Message
-1974-11-14
-static-method
-static-overloaded-int-1
-static-overloaded-String-String
-static-final-field
-static-field
-ONEx
-TWOx
-THREEx
-true
-false
-false
-42
-1
-m
-

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/expected/default-object-wrapper.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/expected/default-object-wrapper.txt b/src/test/resources/org/apache/freemarker/test/templatesuite/expected/default-object-wrapper.txt
new file mode 100644
index 0000000..ab41e26
--- /dev/null
+++ b/src/test/resources/org/apache/freemarker/test/templatesuite/expected/default-object-wrapper.txt
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+array-0
+array-1
+2
+array-0
+array-1
+list-0
+list-1
+list-2
+3
+not empty
+list-0
+value
+objValue
+foo-value
+hasfoo
+nobaz
+bar-value-0
+bar-value-0
+foo-value
+overloaded-int-1
+overloaded-String-String
+Message
+1974-11-14
+static-method
+static-overloaded-int-1
+static-overloaded-String-String
+static-final-field
+static-field
+ONEx
+TWOx
+THREEx
+true
+false
+false
+42
+1
+m
+

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/expected/listhash.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/expected/listhash.txt b/src/test/resources/org/apache/freemarker/test/templatesuite/expected/listhash.txt
index c713195..15b2efe 100644
--- a/src/test/resources/org/apache/freemarker/test/templatesuite/expected/listhash.txt
+++ b/src/test/resources/org/apache/freemarker/test/templatesuite/expected/listhash.txt
@@ -84,37 +84,6 @@ Non-empty maps:
     Map:
     
     [
-      k1 = v1
-      2 = v2
-      k3 = v3
-      null = v4
-      Y = v5
-      N = null
-    ]
-  
-    [
-      k1 = v1; // @0=@0; odd=odd; Y=Y
-      2 = v2; // @1=@1; even=even; Y=Y
-      k3 = v3; // @2=@2; odd=odd; Y=Y
-      null = v4; // @3=@3; even=even; Y=Y
-      Y = v5; // @4=@4; odd=odd; Y=Y
-      N = null // @5=@5; even=even; N=N
-    ]
-  
-    {
-      [
-        k1 = v1; // @0=@0; odd=odd; Y=Y
-        2 = v2; // @1=@1; even=even; Y=Y
-        k3 = v3; // @2=@2; odd=odd; Y=Y
-        null = v4; // @3=@3; even=even; Y=Y
-        Y = v5; // @4=@4; odd=odd; Y=Y
-        N = null // @5=@5; even=even; N=N
-      ]
-    }
-
-    Map:
-    
-    [
       k1 = 11
       k2 = 22
     ]
@@ -173,19 +142,6 @@ Empty maps:
       Empty
     }
 
-    Map:
-    
-    [
-    ]
-  
-    [
-      Empty
-    ]
-  
-    {
-      Empty
-    }
-
 
   a @ 0, 1
     aa = 11  @ 0 // inside a @ 0, 1

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/templates/api-builtins.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/api-builtins.ftl b/src/test/resources/org/apache/freemarker/test/templatesuite/templates/api-builtins.ftl
index ce5fc9f..c915104 100644
--- a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/api-builtins.ftl
+++ b/src/test/resources/org/apache/freemarker/test/templatesuite/templates/api-builtins.ftl
@@ -30,17 +30,11 @@
 </#list>
 <@assertEquals expected="1: a, 2: b, 3: c" actual=dump />
 
-<#assign bw = testName?ends_with("-bw")>
-
 <@assert test=map?has_api />
 <@assert test=list?has_api />
 <@assert test=set?has_api />
-<@assert test = bw == s?has_api />
+<@assert test=!s?has_api />
 <@assert test=!1?has_api />
 <@assert test=!""?has_api />
 <@assert test=!{}?has_api />
 <@assert test=!true?has_api />
-
-<#if bw>
-  <@assertEquals expected="TEST" actual=s?api.toUpperCase() />
-</#if>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/templates/bean-maps.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/bean-maps.ftl b/src/test/resources/org/apache/freemarker/test/templatesuite/templates/bean-maps.ftl
deleted file mode 100644
index e8651b8..0000000
--- a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/bean-maps.ftl
+++ /dev/null
@@ -1,58 +0,0 @@
-[#ftl]
-[#--
-  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.
---]
-
-
-[#macro test hash desc]
-${desc} (${hash?size}):
- [#list hash?keys?sort as key]
-  ${key}[#list 0 .. (16 - key?length) as x] [/#list]: [@print value=hash[key]/]
- [/#list]
-[/#macro]
-
-
-[#macro print value="DEBUGME"]
-  [#if value?is_number || value?is_string]
-    ${value}[#t]
-  [#elseif value?is_boolean]
-    [#if value]true[#else]false[/#if][#t]
-  [#else]
-    UNKNOWN[#t]
-  [/#if]
-[/#macro]
-[@test hash=m1 desc="properties only, shadow"/]
-
-[@test hash=m2 desc="properties only"/]
-
-[@test hash=m3 desc="nothing, shadow"/]
-
-[@test hash=m4 desc="nothing"/]
-
-[@test hash=m5 desc="all, shadow"/]
-
-[@test hash=m6 desc="all"/]
-
-[@test hash=m7 desc="simple map mode"/]
-
-
-String concatenation:
-  ${s1 + s2}
-  ${s3 + s4}
-  ${s1 + s3}
-  ${s2 + s4}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/templates/beans.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/beans.ftl b/src/test/resources/org/apache/freemarker/test/templatesuite/templates/beans.ftl
deleted file mode 100644
index 3b29c62..0000000
--- a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/beans.ftl
+++ /dev/null
@@ -1,60 +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.
--->
-<#list array as item>
-${item}
-</#list>
-${array?size}
-${array[0]}
-${array[1]}
-<#list list as item>
-${item}
-</#list>
-${list.size()}
-<#if list.isEmpty()>empty<#else>not empty</#if>
-${list[0]}
-${map.key}
-${map(objKey)}
-${obj.foo}
-<#if obj.foo?exists>hasfoo<#else>nofoo</#if>
-<#if obj.baz?exists>hasbaz<#else>nobaz</#if>
-${obj.bar[0]}
-${obj.bar(0)}
-${obj.getFoo()}
-${obj.overloaded(1?int)}
-${obj.overloaded("String")}
-${resourceBundle.message}
-${resourceBundle("format", date)}
-<#assign static = statics["org.apache.freemarker.test.templatesuite.models.BeanTestClass"]>
-${static.staticMethod()}
-${static.staticOverloaded[1]}
-${static.staticOverloaded("String")}
-${static.STATIC_FINAL_FIELD}
-${static.STATIC_FIELD}
-<#assign enum = enums["org.apache.freemarker.test.templatesuite.models.EnumTestClass"]>
-${enum.ONE}
-${enum.TWO}
-${enum.THREE}
-${(enum.ONE == enum.ONE)?string("true", "false")}
-${(enum.ONE == enum.TWO)?string("true", "false")}
-${enums["org.apache.freemarker.test.templatesuite.models.BeanTestClass"]?exists?string("true", "false")}
-${obj.something}
-${obj.publicInner.x}
-${obj.publicInner.m()}
-<@assertFails message="obj.privateInner.x">${obj.privateInner.x}</@>
-<@assertFails message="obj.privateInner.m">${obj.privateInner.m()}</@>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/templates/default-object-wrapper.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/default-object-wrapper.ftl b/src/test/resources/org/apache/freemarker/test/templatesuite/templates/default-object-wrapper.ftl
new file mode 100644
index 0000000..d44f26e
--- /dev/null
+++ b/src/test/resources/org/apache/freemarker/test/templatesuite/templates/default-object-wrapper.ftl
@@ -0,0 +1,60 @@
+<#--
+  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.
+-->
+<#list array as item>
+${item}
+</#list>
+${array?size}
+${array[0]}
+${array[1]}
+<#list list as item>
+${item}
+</#list>
+${list?size}
+${list?hasContent?then('not empty', 'empty')}
+${list[0]}
+${map.key}
+${map?api.get(objKey)}
+${obj.foo}
+<#if obj.foo?exists>hasfoo<#else>nofoo</#if>
+<#if obj.baz?exists>hasbaz<#else>nobaz</#if>
+${obj.bar[0]}
+${obj.bar(0)}
+${obj.getFoo()}
+${obj.overloaded(1?int)}
+${obj.overloaded("String")}
+${resourceBundle.message}
+${resourceBundle("format", date)}
+<#assign static = statics["org.apache.freemarker.test.templatesuite.models.BeanTestClass"]>
+${static.staticMethod()}
+${static.staticOverloaded[1]}
+${static.staticOverloaded("String")}
+${static.STATIC_FINAL_FIELD}
+${static.STATIC_FIELD}
+<#assign enum = enums["org.apache.freemarker.test.templatesuite.models.EnumTestClass"]>
+${enum.ONE}
+${enum.TWO}
+${enum.THREE}
+${(enum.ONE == enum.ONE)?string("true", "false")}
+${(enum.ONE == enum.TWO)?string("true", "false")}
+${enums["org.apache.freemarker.test.templatesuite.models.BeanTestClass"]?exists?string("true", "false")}
+${obj.something}
+${obj.publicInner.x}
+${obj.publicInner.m()}
+<@assertFails message="obj.privateInner.x">${obj.privateInner.x}</@>
+<@assertFails message="obj.privateInner.m">${obj.privateInner.m()}</@>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/templates/overloaded-methods.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/overloaded-methods.ftl b/src/test/resources/org/apache/freemarker/test/templatesuite/templates/overloaded-methods.ftl
index 2d85dcc..8cf116f 100644
--- a/src/test/resources/org/apache/freemarker/test/templatesuite/templates/overloaded-methods.ftl
+++ b/src/test/resources/org/apache/freemarker/test/templatesuite/templates/overloaded-methods.ftl
@@ -19,9 +19,7 @@
 
 <@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaStringList) expected="mStringArrayVsListPreference(List [a, b])" />
 <@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaStringArray) expected="mStringArrayVsListPreference(String[] [a, b])" />
-<#if dow>
-  <@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaObjectArray) expected="mStringArrayVsListPreference(List [a, b])" />
-</#if>
+<@assertEquals actual=obj.mStringArrayVsListPreference(obj.javaObjectArray) expected="mStringArrayVsListPreference(List [a, b])" />
 
 <#-- Check if non-overloaded calls still work; they share some code with overloaded methods: -->
 <@assertEquals actual=obj.mIntArrayNonOverloaded([1, 2, 3]) expected="mIntArrayNonOverloaded(int[] [1, 2, 3])" />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml b/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml
index c573c72..e3be657 100644
--- a/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml
+++ b/src/test/resources/org/apache/freemarker/test/templatesuite/testcases.xml
@@ -56,23 +56,10 @@ Note that for the incompatible_improvements setting you can specify a list of ve
    <setting input_encoding="UTF-8" output_encoding="UTF-8" />
    
    <testCase name="api-builtins" noOutput="true">
-      <setting
-            object_wrapper="DefaultObjectWrapper(3.0.0)"
-            api_builtin_enabled="true"
-      />
-   </testCase>
-   <testCase name="api-builtins[#endTN]-bw" noOutput="true">
-      <setting
-            object_wrapper="BeansWrapper(3.0.0)"
-            api_builtin_enabled="true"
-      />
+      <setting api_builtin_enabled="true" />
    </testCase>
    <testCase name="arithmetic" />
    <testCase name="assignments" noOutput="true" />
-   <testCase name="bean-maps"/>
-   <testCase name="beans">
-      <setting object_wrapper="beans"/>
-   </testCase>
    <testCase name="boolean" />
    <testCase name="charset-in-header">
       <setting clear_encoding_map="Y" input_encoding="ISO-8859-5" />
@@ -86,9 +73,10 @@ Note that for the incompatible_improvements setting you can specify a list of ve
    <testCase name="dateformat-iso-bi" noOutput="true" />
    <testCase name="dateparsing" noOutput="true" />
    <testCase name="default"/>
-   <testCase name="default-xmlns">
-      <setting object_wrapper="beans"/>
+   <testCase name="default-object-wrapper">
+      <setting api_builtin_enabled="true" />
    </testCase>
+   <testCase name="default-xmlns" />
    <testCase name="encoding-builtins" />
    <testCase name="escapes" />
    <testCase name="hashliteral" />
@@ -158,9 +146,6 @@ Note that for the incompatible_improvements setting you can specify a list of ve
    <testCase name="recover" />
    <testCase name="root" />
    <testCase name="setting" noOutput="true" />
-   <testCase name="sequence-builtins[#endTN]-with-BeansWrapper" expected="sequence-builtins.txt">
-      <setting object_wrapper="beans"/> 
-   </testCase>
    <testCase name="sequence-builtins[#endTN]-with-SimpleTemplateModel" expected="sequence-builtins.txt">
       <setting object_wrapper="org.apache.freemarker.test.util.SimpleMapAndCollectionObjectWrapper(3.0.0)" />
    </testCase>
@@ -190,22 +175,12 @@ Note that for the incompatible_improvements setting you can specify a list of ve
    <testCase name="variables"/>
    <testCase name="whitespace-trim"/>
    <testCase name="wstrip-in-header"/>
-   <testCase name="xml-fragment">
-      <setting object_wrapper="beans"/> 
-   </testCase>
-   <testCase name="xmlns1">
-      <setting object_wrapper="beans"/>
-   </testCase>
+   <testCase name="xml-fragment" />
+   <testCase name="xmlns1" />
    <testCase name="xmlns2" template="xmlns1.ftl"  expected="xmlns1.txt" />
-   <testCase name="xmlns3">
-      <setting object_wrapper="beans"/>
-   </testCase>   
-   <testCase name="xmlns4">
-      <setting object_wrapper="beans"/>
-   </testCase>   
-   <testCase name="xmlns5">
-      <setting object_wrapper="beans"/>
-   </testCase>   
+   <testCase name="xmlns3" />
+   <testCase name="xmlns4" />
+   <testCase name="xmlns5" />
    <testCase name="xml-ns_prefix-scope" template="xml-ns_prefix-scope-main.ftl" />
    <testCase name="hashconcat"/>
    <testCase name="new-defaultresolver" />
@@ -230,16 +205,10 @@ Note that for the incompatible_improvements setting you can specify a list of ve
    <testCase name="number-math-builtins" noOutput="true" />
    <testCase name="string-builtin-coercion" noOutput="true" />
    
-   <testCase name="overloaded-methods[#endTN]-inc-bw" noOutput="true">
-      <setting object_wrapper="org.apache.freemarker.core.model.impl.beans.BeansWrapperInc"/>
-   </testCase>
-   <testCase name="overloaded-methods[#endTN]-desc-bw" noOutput="true">
-      <setting object_wrapper="org.apache.freemarker.core.model.impl.beans.BeansWrapperDesc"/>
-   </testCase>
    <testCase name="overloaded-methods[#endTN]-inc-dow" noOutput="true">
-      <setting object_wrapper="org.apache.freemarker.core.model.impl.beans.DefaultObjectWrapperInc"/>
+      <setting object_wrapper="org.apache.freemarker.core.model.impl.DefaultObjectWrapperInc"/>
    </testCase>
    <testCase name="overloaded-methods[#endTN]-desc-dow" noOutput="true">
-      <setting object_wrapper="org.apache.freemarker.core.model.impl.beans.DefaultObjectWrapperDesc"/>
+      <setting object_wrapper="org.apache.freemarker.core.model.impl.DefaultObjectWrapperDesc"/>
    </testCase>
 </testCases>


[08/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/package.html
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/package.html b/src/main/java/org/apache/freemarker/core/model/impl/beans/package.html
deleted file mode 100644
index bd6b36c..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/package.html
+++ /dev/null
@@ -1,39 +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.
--->
-<html>
-<head>
-<title></title>
-</head>
-<body>
-
-<p>The {@linkplain org.apache.freemarker.core.model.impl.DefaultObjectWrapper default object wrapper} of FreeMarker uses
-this to expose Java Beans and POJO-s to templates.</p>
-
-<p>Most of the issues dealing with beans are handled by the 
-{@link freemarker.ext.beans.BeansWrapper#wrap(Object)}and {@link 
-freemarker.ext.beans.BeansWrapper#getStaticModels()} methods. In normal cases, 
- these are the only methods
-you should use to turn an arbitrary Java object into a 
-FreeMarker {@link freemarker.template.TemplateModel}. Additionally, you can manually create 
-instance of any wrapper class using its constructors.
-Note, however that in such cases you bypass the eventual model caching
-of the wrapper.</p>
-
-</body>
-</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/package.html
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/package.html b/src/main/java/org/apache/freemarker/core/model/impl/package.html
new file mode 100644
index 0000000..100c5c5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/package.html
@@ -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.
+-->
+<html>
+<head>
+<title></title>
+</head>
+<body>
+
+<p>The {@linkplain org.apache.freemarker.core.model.impl.DefaultObjectWrapper default object wrapper} of FreeMarker uses
+this to expose Java Beans and POJO-s to templates.</p>
+
+<p>Most of the issues dealing with beans are handled by the 
+{@link freemarker.ext.beans.DefaultObjectWrapper#wrap(Object)}and {@link
+freemarker.ext.beans.DefaultObjectWrapper#getStaticModels()} methods. In normal cases,
+ these are the only methods
+you should use to turn an arbitrary Java object into a 
+FreeMarker {@link freemarker.template.TemplateModel}. Additionally, you can manually create 
+instance of any wrapper class using its constructors.
+Note, however that in such cases you bypass the eventual model caching
+of the wrapper.</p>
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/util/FTLUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/FTLUtil.java b/src/main/java/org/apache/freemarker/core/util/FTLUtil.java
index 9f0cca7..eba25f6 100644
--- a/src/main/java/org/apache/freemarker/core/util/FTLUtil.java
+++ b/src/main/java/org/apache/freemarker/core/util/FTLUtil.java
@@ -43,17 +43,17 @@ import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.TemplateTransformModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
-import org.apache.freemarker.core.model.impl.beans.BeanModel;
-import org.apache.freemarker.core.model.impl.beans.BooleanModel;
-import org.apache.freemarker.core.model.impl.beans.CollectionModel;
-import org.apache.freemarker.core.model.impl.beans.DateModel;
-import org.apache.freemarker.core.model.impl.beans.EnumerationModel;
-import org.apache.freemarker.core.model.impl.beans.IteratorModel;
-import org.apache.freemarker.core.model.impl.beans.MapModel;
-import org.apache.freemarker.core.model.impl.beans.NumberModel;
-import org.apache.freemarker.core.model.impl.beans.OverloadedMethodsModel;
-import org.apache.freemarker.core.model.impl.beans.SimpleMethodModel;
-import org.apache.freemarker.core.model.impl.beans.StringModel;
+import org.apache.freemarker.core.model.impl.BeanModel;
+import org.apache.freemarker.core.model.impl.BooleanModel;
+import org.apache.freemarker.core.model.impl.CollectionModel;
+import org.apache.freemarker.core.model.impl.DateModel;
+import org.apache.freemarker.core.model.impl.EnumerationModel;
+import org.apache.freemarker.core.model.impl.IteratorModel;
+import org.apache.freemarker.core.model.impl.MapModel;
+import org.apache.freemarker.core.model.impl.NumberModel;
+import org.apache.freemarker.core.model.impl.OverloadedMethodsModel;
+import org.apache.freemarker.core.model.impl.SimpleMethodModel;
+import org.apache.freemarker.core.model.impl.StringModel;
 
 /**
  * Static utility methods that perform tasks specific to the FreeMarker Template Language (FTL).

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/util/_ClassUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_ClassUtil.java b/src/main/java/org/apache/freemarker/core/util/_ClassUtil.java
index 0401ce0..f97d186 100644
--- a/src/main/java/org/apache/freemarker/core/util/_ClassUtil.java
+++ b/src/main/java/org/apache/freemarker/core/util/_ClassUtil.java
@@ -19,7 +19,7 @@
 
 package org.apache.freemarker.core.util;
 
-import org.apache.freemarker.core.model.impl.beans.NumberModel;
+import org.apache.freemarker.core.model.impl.NumberModel;
 
 public class _ClassUtil {
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 25d09e5..0016241 100644
--- a/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
+++ b/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
@@ -411,7 +411,6 @@ public class FreemarkerServlet extends HttpServlet {
     private static final String DEPR_INITPARAM_ENCODING = "DefaultEncoding";
     private static final String DEPR_INITPARAM_OBJECT_WRAPPER = "ObjectWrapper";
     private static final String DEPR_INITPARAM_WRAPPER_SIMPLE = "simple";
-    private static final String DEPR_INITPARAM_WRAPPER_BEANS = "beans";
     private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER = "TemplateExceptionHandler";
     private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_RETHROW = "rethrow";
     private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_DEBUG = "debug";
@@ -1298,9 +1297,6 @@ public class FreemarkerServlet extends HttpServlet {
                         + Configurable.OBJECT_WRAPPER_KEY + " and "
                         + DEPR_INITPARAM_OBJECT_WRAPPER);
             }
-            if (DEPR_INITPARAM_WRAPPER_BEANS.equals(wrapper)) {
-                return _StaticObjectWrappers.BEANS_WRAPPER;
-            }
             if (DEPR_INITPARAM_WRAPPER_SIMPLE.equals(wrapper)) {
                 return _StaticObjectWrappers.SIMPLE_OBJECT_WRAPPER;
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 956064a..e9e9bf4 100644
--- a/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
+++ b/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
@@ -33,7 +33,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.TemplateTransformModel;
-import org.apache.freemarker.core.model.impl.beans.SimpleMethodModel;
+import org.apache.freemarker.core.model.impl.SimpleMethodModel;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._ClassUtil;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/servlet/jsp/JspContextModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/jsp/JspContextModel.java b/src/main/java/org/apache/freemarker/servlet/jsp/JspContextModel.java
index dcd7fcd..d07796c 100644
--- a/src/main/java/org/apache/freemarker/servlet/jsp/JspContextModel.java
+++ b/src/main/java/org/apache/freemarker/servlet/jsp/JspContextModel.java
@@ -46,7 +46,7 @@ implements
     @Override
     public TemplateModel get(String key) throws TemplateModelException {
         Object bean = scope == ANY_SCOPE ? pageContext.findAttribute(key) : pageContext.getAttribute(key, scope);
-        return _StaticObjectWrappers.BEANS_WRAPPER.wrap(bean);
+        return _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER.wrap(bean);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java b/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
index 4711cd2..526d3d5 100644
--- a/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
+++ b/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
@@ -40,7 +40,7 @@ import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.util._StringUtil;
 import org.apache.freemarker.servlet.jsp.SimpleTagDirectiveModel.TemplateExceptionWrapperJspException;
 
@@ -84,7 +84,7 @@ class JspTagModelBase {
         if (args != null && !args.isEmpty()) {
             ObjectWrapperAndUnwrapper unwrapper = 
                     wrapper instanceof ObjectWrapperAndUnwrapper ? (ObjectWrapperAndUnwrapper) wrapper
-                            : _StaticObjectWrappers.BEANS_WRAPPER;  // [2.4] Throw exception in this case
+                            : _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER;  // [2.4] Throw exception in this case
             final Object[] argArray = new Object[1];
             for (Iterator iter = args.entrySet().iterator(); iter.hasNext(); ) {
                 final Map.Entry entry = (Map.Entry) iter.next();
@@ -102,7 +102,7 @@ class JspTagModelBase {
                     }
                 } else {
                     if (arg instanceof BigDecimal) {
-                        argArray[0] = BeansWrapper.coerceBigDecimal(
+                        argArray[0] = DefaultObjectWrapper.coerceBigDecimal(
                                 (BigDecimal) arg, setterMethod.getParameterTypes()[0]);
                     }
                     try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 930223c..67b336e 100644
--- a/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
+++ b/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
@@ -68,7 +68,6 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateTransformModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.core.util._NullArgumentException;
@@ -306,7 +305,7 @@ public class TaglibFactory implements TemplateHashModel {
      * Sets the {@link ObjectWrapper} used when building the JSP tag library {@link TemplateHashModel}-s from the TLD-s.
      * Usually, it should be the same {@link ObjectWrapper} that will be used inside the templates. {@code null} value
      * is only supported for backward compatibility. For custom EL functions to be exposed, it must be non-{@code null}
-     * and an {@code intanceof} {@link BeansWrapper} (like typically, a {@link DefaultObjectWrapper}).
+     * and an {@code intanceof} {@link DefaultObjectWrapper} (like typically, a {@link DefaultObjectWrapper}).
      * 
      * @since 2.3.22
      */
@@ -1624,7 +1623,7 @@ public class TaglibFactory implements TemplateHashModel {
         private static final String E_LISTENER = "listener";
         private static final String E_LISTENER_CLASS = "listener-class";
 
-        private final BeansWrapper beansWrapper;
+        private final DefaultObjectWrapper defaultObjectWrapper;
 
         private final Map<String, TemplateModel> tagsAndFunctions = new HashMap<>();
         private final List listeners = new ArrayList();
@@ -1642,16 +1641,16 @@ public class TaglibFactory implements TemplateHashModel {
         private String listenerClassCData;
 
         TldParserForTaglibBuilding(ObjectWrapper wrapper) {
-            if (wrapper instanceof BeansWrapper) {
-                beansWrapper = (BeansWrapper) wrapper;
+            if (wrapper instanceof DefaultObjectWrapper) {
+                defaultObjectWrapper = (DefaultObjectWrapper) wrapper;
             } else {
-                beansWrapper = null;
+                defaultObjectWrapper = null;
                 if (LOG.isWarnEnabled()) {
                     LOG.warn("Custom EL functions won't be loaded because "
                             + (wrapper == null
                                     ? "no ObjectWrapper was specified for the TaglibFactory "
                                             + "(via TaglibFactory.setObjectWrapper(...), exists since 2.3.22)"
-                                    : "the ObjectWrapper wasn't instance of " + BeansWrapper.class.getName())
+                                    : "the ObjectWrapper wasn't instance of " + DefaultObjectWrapper.class.getName())
                             + ".");
                 }
             }
@@ -1747,7 +1746,7 @@ public class TaglibFactory implements TemplateHashModel {
 
                     tagNameCData = null;
                     tagClassCData = null;
-                } else if (E_FUNCTION.equals(qName) && beansWrapper != null) {
+                } else if (E_FUNCTION.equals(qName) && defaultObjectWrapper != null) {
                     checkChildElementNotNull(qName, E_FUNCTION_CLASS, functionClassCData);
                     checkChildElementNotNull(qName, E_FUNCTION_SIGNATURE, functionSignatureCData);
                     checkChildElementNotNull(qName, E_NAME, functionNameCData);
@@ -1777,7 +1776,7 @@ public class TaglibFactory implements TemplateHashModel {
 
                     final TemplateMethodModelEx elFunctionModel;
                     try {
-                        elFunctionModel = beansWrapper.wrap(null, functionMethod);
+                        elFunctionModel = defaultObjectWrapper.wrap(null, functionMethod);
                     } catch (Exception e) {
                         throw new TldParsingSAXException(
                                 "FreeMarker object wrapping failed on method : " + functionMethod,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/resources/org/apache/freemarker/core/model/impl/beans/unsafeMethods.properties
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/freemarker/core/model/impl/beans/unsafeMethods.properties b/src/main/resources/org/apache/freemarker/core/model/impl/beans/unsafeMethods.properties
deleted file mode 100644
index f1f4e18..0000000
--- a/src/main/resources/org/apache/freemarker/core/model/impl/beans/unsafeMethods.properties
+++ /dev/null
@@ -1,98 +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.
-
-java.lang.Object.wait()
-java.lang.Object.wait(long)
-java.lang.Object.wait(long,int)
-java.lang.Object.notify()
-java.lang.Object.notifyAll()
-
-java.lang.Class.getClassLoader()
-java.lang.Class.newInstance()
-java.lang.Class.forName(java.lang.String)
-java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)
-
-java.lang.reflect.Constructor.newInstance([Ljava.lang.Object;)
-
-java.lang.reflect.Method.invoke(java.lang.Object,[Ljava.lang.Object;)
-
-java.lang.reflect.Field.set(java.lang.Object,java.lang.Object)
-java.lang.reflect.Field.setBoolean(java.lang.Object,boolean)
-java.lang.reflect.Field.setByte(java.lang.Object,byte)
-java.lang.reflect.Field.setChar(java.lang.Object,char)
-java.lang.reflect.Field.setDouble(java.lang.Object,double)
-java.lang.reflect.Field.setFloat(java.lang.Object,float)
-java.lang.reflect.Field.setInt(java.lang.Object,int)
-java.lang.reflect.Field.setLong(java.lang.Object,long)
-java.lang.reflect.Field.setShort(java.lang.Object,short)
-
-java.lang.reflect.AccessibleObject.setAccessible([Ljava.lang.reflect.AccessibleObject;,boolean)
-java.lang.reflect.AccessibleObject.setAccessible(boolean)
-
-java.lang.Thread.destroy()
-java.lang.Thread.getContextClassLoader()
-java.lang.Thread.interrupt()
-java.lang.Thread.join()
-java.lang.Thread.join(long)
-java.lang.Thread.join(long,int)
-java.lang.Thread.resume()
-java.lang.Thread.run()
-java.lang.Thread.setContextClassLoader(java.lang.ClassLoader)
-java.lang.Thread.setDaemon(boolean)
-java.lang.Thread.setName(java.lang.String)
-java.lang.Thread.setPriority(int)
-java.lang.Thread.sleep(long)
-java.lang.Thread.sleep(long,int)
-java.lang.Thread.start()
-java.lang.Thread.stop()
-java.lang.Thread.stop(java.lang.Throwable)
-java.lang.Thread.suspend()
-
-java.lang.ThreadGroup.allowThreadSuspension(boolean)
-java.lang.ThreadGroup.destroy()
-java.lang.ThreadGroup.interrupt()
-java.lang.ThreadGroup.resume()
-java.lang.ThreadGroup.setDaemon(boolean)
-java.lang.ThreadGroup.setMaxPriority(int)
-java.lang.ThreadGroup.stop()
-java.lang.Thread.suspend()
-
-java.lang.Runtime.addShutdownHook(java.lang.Thread)
-java.lang.Runtime.exec(java.lang.String)
-java.lang.Runtime.exec([Ljava.lang.String;)
-java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;)
-java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;,java.io.File)
-java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;)
-java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;,java.io.File)
-java.lang.Runtime.exit(int)
-java.lang.Runtime.halt(int)
-java.lang.Runtime.load(java.lang.String)
-java.lang.Runtime.loadLibrary(java.lang.String)
-java.lang.Runtime.removeShutdownHook(java.lang.Thread)
-java.lang.Runtime.traceInstructions(boolean)
-java.lang.Runtime.traceMethodCalls(boolean)
-
-java.lang.System.exit(int)
-java.lang.System.load(java.lang.String)
-java.lang.System.loadLibrary(java.lang.String)
-java.lang.System.runFinalizersOnExit(boolean)
-java.lang.System.setErr(java.io.PrintStream)
-java.lang.System.setIn(java.io.InputStream)
-java.lang.System.setOut(java.io.PrintStream)
-java.lang.System.setProperties(java.util.Properties)
-java.lang.System.setProperty(java.lang.String,java.lang.String)
-java.lang.System.setSecurityManager(java.lang.SecurityManager)

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties b/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties
new file mode 100644
index 0000000..f1f4e18
--- /dev/null
+++ b/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties
@@ -0,0 +1,98 @@
+# 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.
+
+java.lang.Object.wait()
+java.lang.Object.wait(long)
+java.lang.Object.wait(long,int)
+java.lang.Object.notify()
+java.lang.Object.notifyAll()
+
+java.lang.Class.getClassLoader()
+java.lang.Class.newInstance()
+java.lang.Class.forName(java.lang.String)
+java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)
+
+java.lang.reflect.Constructor.newInstance([Ljava.lang.Object;)
+
+java.lang.reflect.Method.invoke(java.lang.Object,[Ljava.lang.Object;)
+
+java.lang.reflect.Field.set(java.lang.Object,java.lang.Object)
+java.lang.reflect.Field.setBoolean(java.lang.Object,boolean)
+java.lang.reflect.Field.setByte(java.lang.Object,byte)
+java.lang.reflect.Field.setChar(java.lang.Object,char)
+java.lang.reflect.Field.setDouble(java.lang.Object,double)
+java.lang.reflect.Field.setFloat(java.lang.Object,float)
+java.lang.reflect.Field.setInt(java.lang.Object,int)
+java.lang.reflect.Field.setLong(java.lang.Object,long)
+java.lang.reflect.Field.setShort(java.lang.Object,short)
+
+java.lang.reflect.AccessibleObject.setAccessible([Ljava.lang.reflect.AccessibleObject;,boolean)
+java.lang.reflect.AccessibleObject.setAccessible(boolean)
+
+java.lang.Thread.destroy()
+java.lang.Thread.getContextClassLoader()
+java.lang.Thread.interrupt()
+java.lang.Thread.join()
+java.lang.Thread.join(long)
+java.lang.Thread.join(long,int)
+java.lang.Thread.resume()
+java.lang.Thread.run()
+java.lang.Thread.setContextClassLoader(java.lang.ClassLoader)
+java.lang.Thread.setDaemon(boolean)
+java.lang.Thread.setName(java.lang.String)
+java.lang.Thread.setPriority(int)
+java.lang.Thread.sleep(long)
+java.lang.Thread.sleep(long,int)
+java.lang.Thread.start()
+java.lang.Thread.stop()
+java.lang.Thread.stop(java.lang.Throwable)
+java.lang.Thread.suspend()
+
+java.lang.ThreadGroup.allowThreadSuspension(boolean)
+java.lang.ThreadGroup.destroy()
+java.lang.ThreadGroup.interrupt()
+java.lang.ThreadGroup.resume()
+java.lang.ThreadGroup.setDaemon(boolean)
+java.lang.ThreadGroup.setMaxPriority(int)
+java.lang.ThreadGroup.stop()
+java.lang.Thread.suspend()
+
+java.lang.Runtime.addShutdownHook(java.lang.Thread)
+java.lang.Runtime.exec(java.lang.String)
+java.lang.Runtime.exec([Ljava.lang.String;)
+java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;)
+java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;,java.io.File)
+java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;)
+java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;,java.io.File)
+java.lang.Runtime.exit(int)
+java.lang.Runtime.halt(int)
+java.lang.Runtime.load(java.lang.String)
+java.lang.Runtime.loadLibrary(java.lang.String)
+java.lang.Runtime.removeShutdownHook(java.lang.Thread)
+java.lang.Runtime.traceInstructions(boolean)
+java.lang.Runtime.traceMethodCalls(boolean)
+
+java.lang.System.exit(int)
+java.lang.System.load(java.lang.String)
+java.lang.System.loadLibrary(java.lang.String)
+java.lang.System.runFinalizersOnExit(boolean)
+java.lang.System.setErr(java.io.PrintStream)
+java.lang.System.setIn(java.io.InputStream)
+java.lang.System.setOut(java.io.PrintStream)
+java.lang.System.setProperties(java.util.Properties)
+java.lang.System.setProperty(java.lang.String,java.lang.String)
+java.lang.System.setSecurityManager(java.lang.SecurityManager)

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 9486c33..72498a6 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -73,9 +73,7 @@ the FreeMarer 3 changelog here:
   OutputFormat realted classes were moved to org.apache.freemarker.core.outputformat.
   ValueFormat related classes were moved to org.apache.freemarker.core.valueformat.
   ArithmeticEngine related classes were moved to org.apache.freemarker.core.arithmetic.
-  freemarker.ext.beans were moved under org.apache.freemarker.core.model.impl.beans for now (but later
-  we only want a DefaultObject wrapper, no BeansWrapper, so this will change) and freemarker.ext.dom
-  was moved to org.apache.freemarker.dom.
+  freemarker.ext.dom was moved into org.apache.freemarker.dom.
 - Moved the all the static final ObjectWrapper-s to the new _StaticObjectWrappers class, and made them
   write protected (non-configurable). Also now they come from the pool that ObjectWrapper builders use.
 - WrappingTemplateModel.objectWrapper is now final, and its statically stored default value can't be set anymore.
@@ -119,4 +117,4 @@ the FreeMarer 3 changelog here:
   Also, on the places where ParseException was used for other than template   parsing, o.a.f.core.util.GenericParseException is used now instead,
   which doesn't have the template parsing related fields that we can't fill.
 - Removed DefaultObjectWrapper settings that only exist so that you can set backward compatible behavior instead of the
-  recommended value: useAdaptersForContainers, forceLegacyNonListCollections, iterableSupport
\ No newline at end of file
+  recommended value: useAdaptersForContainers, forceLegacyNonListCollections, iterableSupport, simpleMapWrapper
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 8413770..ee4bcbd 100644
--- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -42,10 +42,9 @@ import org.apache.freemarker.core.Configurable.UnknownSettingException;
 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.SimpleScalar;
 import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
-import org.apache.freemarker.core.model.impl.beans.StringModel;
 import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.outputformat.UnregisteredOutputFormatException;
@@ -1081,12 +1080,12 @@ public class ConfigurationTest extends TestCase {
         cfg.setSharedVariable("b", "bbLegacy");
         
         // Cause re-wrapping of variables added via setSharedVaribles:
-        cfg.setObjectWrapper(new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build());
+        cfg.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build());
 
         {
             TemplateScalarModel aVal = (TemplateScalarModel) cfg.getSharedVariable("a");
             assertEquals("aa", aVal.getAsString());
-            assertEquals(StringModel.class, aVal.getClass());
+            assertEquals(SimpleScalar.class, aVal.getClass());
             
             TemplateScalarModel bVal = (TemplateScalarModel) cfg.getSharedVariable("b");
             assertEquals("bbLegacy", bVal.getAsString());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 224ff7d..aa9e66b 100644
--- a/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
+++ b/src/test/java/org/apache/freemarker/core/IteratorIssuesTest.java
@@ -23,52 +23,37 @@ import java.util.Iterator;
 
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
 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 String FTL_HAS_CONTENT_AND_LIST
             = "<#if it?hasContent><#list it as i>${i}</#list><#else>empty</#if>";
     private static final String OUT_HAS_CONTENT_AND_LIST_ABC = "abc";
     private static final String OUT_HAS_CONTENT_AND_LIST_EMPTY = "empty";
-    
+
     private static final String FTL_LIST_AND_HAS_CONTENT
             = "<#list it as i>${i}${it?hasContent?then('+', '-')}</#list>";
     private static final String OUT_LIST_AND_HAS_CONTENT_BW_GOOD = "a+b+c-";
 
     @Test
-    public void testHasContentAndListDOW() throws Exception {
-        addToDataModel("it", getDOW300().wrap(getAbcIt()));
+    public void testHasContentAndList() throws Exception {
+        addToDataModel("it", OW.wrap(getAbcIt()));
         assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
-        
-        addToDataModel("it", getDOW300().wrap(getEmptyIt()));
-        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
-    }
 
-    @Test
-    public void testHasContentAndListBW() throws Exception {
-        addToDataModel("it", getBW300().wrap(getAbcIt()));
-        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
-        
-        addToDataModel("it", getBW300().wrap(getEmptyIt()));
+        addToDataModel("it", OW.wrap(getEmptyIt()));
         assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
     }
-    
+
     @Test
-    public void testListAndHasContentDOW() throws Exception {
-        addToDataModel("it", getDOW300().wrap(getAbcIt()));
+    public void testListAndHasContent() throws Exception {
+        addToDataModel("it", OW.wrap(getAbcIt()));
         assertErrorContains(FTL_LIST_AND_HAS_CONTENT, "can be listed only once");
     }
 
-    @Test
-    public void testListAndHasContentBW() throws Exception {
-        addToDataModel("it", getBW300().wrap(getAbcIt()));
-        assertOutput(FTL_LIST_AND_HAS_CONTENT, OUT_LIST_AND_HAS_CONTENT_BW_GOOD);
-    }
-    
     private Iterator getAbcIt() {
         return Arrays.asList(new String[] { "a", "b", "c" }).iterator();
     }
@@ -76,13 +61,5 @@ public class IteratorIssuesTest extends TemplateTest {
     private Iterator getEmptyIt() {
         return Arrays.asList(new String[] {  }).iterator();
     }
-    
-    private DefaultObjectWrapper getDOW300() {
-        return new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
-    }
 
-    private BeansWrapper getBW300() {
-        return new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 5a702d4..ce91c66 100644
--- a/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
@@ -42,7 +42,6 @@ import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
 import org.apache.freemarker.core.templateresolver.CacheStorage;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.TemplateLoaderSession;
@@ -359,24 +358,23 @@ public class ObjectBuilderSettingsTest {
             assertNull(res.b4.b4);
         }
     }
-    
+
     @Test
-    public void beansWrapperTest() throws Exception {
-        BeansWrapper bw = (BeansWrapper) _ObjectBuilderSettingEvaluator.eval(
-                "BeansWrapper(3.0.0, simpleMapWrapper=true, exposeFields=true)",
+    public void defaultObjectWrapperTest() throws Exception {
+        DefaultObjectWrapper ow = (DefaultObjectWrapper) _ObjectBuilderSettingEvaluator.eval(
+                "DefaultObjectWrapper(3.0.0)",
                 ObjectWrapper.class, false, _SettingEvaluationEnvironment.getCurrent());
-        assertEquals(Configuration.VERSION_3_0_0, bw.getIncompatibleImprovements());
-        assertTrue(bw.isSimpleMapWrapper());
-        assertTrue(bw.isExposeFields());
+        assertEquals(Configuration.VERSION_3_0_0, ow.getIncompatibleImprovements());
+        assertFalse(ow.isExposeFields());
     }
 
     @Test
-    public void defaultObjectWrapperTest() throws Exception {
-        DefaultObjectWrapper bw = (DefaultObjectWrapper) _ObjectBuilderSettingEvaluator.eval(
-                "DefaultObjectWrapper(3.0.0)",
+    public void defaultObjectWrapperTest2() throws Exception {
+        DefaultObjectWrapper ow = (DefaultObjectWrapper) _ObjectBuilderSettingEvaluator.eval(
+                "DefaultObjectWrapper(3.0.0, exposeFields=true)",
                 ObjectWrapper.class, false, _SettingEvaluationEnvironment.getCurrent());
-        assertEquals(Configuration.VERSION_3_0_0, bw.getIncompatibleImprovements());
-        assertFalse(bw.isExposeFields());
+        assertEquals(Configuration.VERSION_3_0_0, ow.getIncompatibleImprovements());
+        assertTrue(ow.isExposeFields());
     }
 
     @Test
@@ -386,7 +384,7 @@ public class ObjectBuilderSettingsTest {
         {
             Properties props = new Properties();
             props.setProperty(Configurable.OBJECT_WRAPPER_KEY,
-                    "org.apache.freemarker.core.model.impl.beans.BeansWrapper(3.0.0)");
+                    "org.apache.freemarker.core.model.impl.DefaultObjectWrapper(3.0.0)");
             props.setProperty(Configurable.ARITHMETIC_ENGINE_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyArithmeticEngine");
             props.setProperty(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY,
@@ -399,10 +397,10 @@ public class ObjectBuilderSettingsTest {
             props.setProperty(Configuration.TEMPLATE_LOADER_KEY,
                     "org.apache.freemarker.core.ObjectBuilderSettingsTest$DummyTemplateLoader()");
             cfg.setSettings(props);
-            assertEquals(BeansWrapper.class, cfg.getObjectWrapper().getClass());
+            assertEquals(DefaultObjectWrapper.class, cfg.getObjectWrapper().getClass());
             assertTrue(((WriteProtectable) cfg.getObjectWrapper()).isWriteProtected());
             assertEquals(
-                    Configuration.VERSION_3_0_0, ((BeansWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
+                    Configuration.VERSION_3_0_0, ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
             assertEquals(DummyArithmeticEngine.class, cfg.getArithmeticEngine().getClass());
             assertEquals(DummyTemplateExceptionHandler.class, cfg.getTemplateExceptionHandler().getClass());
             assertEquals(DummyCacheStorage.class, cfg.getCacheStorage().getClass());
@@ -428,7 +426,7 @@ public class ObjectBuilderSettingsTest {
             assertEquals(1, ((DummyArithmeticEngine) cfg.getArithmeticEngine()).getX());
             assertEquals(1, ((DummyTemplateExceptionHandler) cfg.getTemplateExceptionHandler()).getX());
             assertEquals(Configuration.VERSION_3_0_0,
-                    ((BeansWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
+                    ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
             assertEquals(500, ((MruCacheStorage) cfg.getCacheStorage()).getSoftSizeLimit());
             assertEquals(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER, cfg.getNewBuiltinClassResolver());
             assertEquals("utf-8", cfg.getDefaultEncoding());
@@ -436,16 +434,16 @@ public class ObjectBuilderSettingsTest {
 
         {
             Properties props = new Properties();
-            props.setProperty(Configurable.OBJECT_WRAPPER_KEY, "Beans");
+            props.setProperty(Configurable.OBJECT_WRAPPER_KEY, "Default");
             props.setProperty(Configurable.ARITHMETIC_ENGINE_KEY, "bigdecimal");
             props.setProperty(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY, "rethrow");
             cfg.setSettings(props);
-            assertEquals(BeansWrapper.class, cfg.getObjectWrapper().getClass());
+            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,
-                    ((BeansWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
+                    ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
         }
         
         {
@@ -456,7 +454,7 @@ public class ObjectBuilderSettingsTest {
             assertTrue(((WriteProtectable) cfg.getObjectWrapper()).isWriteProtected());
             assertEquals(
                     Configuration.VERSION_3_0_0,
-                    ((BeansWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
+                    ((DefaultObjectWrapper) cfg.getObjectWrapper()).getIncompatibleImprovements());
         }
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..b555e59
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/AbstractParallelIntrospectionTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+import junit.framework.TestCase;
+
+public abstract class AbstractParallelIntrospectionTest extends TestCase {
+    
+    private static final int NUM_THREADS = 8;
+    private static final int NUM_ENTITYES = 8;
+    private static final int NUM_MEMBERS = 8;
+    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);
+    
+    public AbstractParallelIntrospectionTest(String name) {
+        super(name);
+    }
+    
+    public void testReliability() {
+        testReliability(ITERATIONS);
+    }
+    
+    public void testReliability(int iterations) {
+        TestThread[] ts = new TestThread[NUM_THREADS]; 
+        for (int i = 0; i < NUM_THREADS; i++) {
+            ts[i] = new TestThread(iterations);
+            ts[i].start();
+        }
+
+        for (int i = 0; i < NUM_THREADS; i++) {
+            try {
+                ts[i].join();
+                if (ts[i].error != null) {
+                    throw new AssertionError(ts[i].error);
+                }
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    protected abstract TemplateHashModel getWrappedEntity(int objIdx) throws TemplateModelException;
+    
+    protected final DefaultObjectWrapper getObjectWrapper() {
+        return ow;
+    }
+    
+    private class TestThread extends Thread {
+        
+        private final int iterations;
+        
+        private Throwable error;
+        
+        private TestThread(int iterations) {
+            this.iterations = iterations;
+        }
+
+        @Override
+        public void run() {
+            try {
+                for (int i = 0; i < iterations; i++) {
+                    if (Math.random() < CACHE_CLEARING_CHANCE) {
+                        ow.clearClassIntrospecitonCache();
+                    }
+                    int objIdx = (int) (Math.random() * NUM_ENTITYES);
+                    TemplateHashModel h = getWrappedEntity(objIdx);
+                    int mIdx = (int) (Math.random() * NUM_MEMBERS);
+                    testProperty(h, objIdx, mIdx);
+                    testMethod(h, objIdx, mIdx);
+                }
+            } catch (Throwable e) {
+                error = e;
+            }
+        }
+
+        private void testProperty(TemplateHashModel h, int objIdx, int mIdx)
+                throws TemplateModelException, AssertionError {
+            TemplateNumberModel pv = (TemplateNumberModel) h.get("p" + mIdx);
+            final int expected = objIdx * 1000 + mIdx;
+            final int got = pv.getAsNumber().intValue();
+            if (got != expected) {
+                throw new AssertionError("Property assertation failed; " +
+                        "expected " + expected + ", but got " + got);
+            }
+        }
+
+        private void testMethod(TemplateHashModel h, int objIdx, int mIdx)
+                throws TemplateModelException, AssertionError {
+            TemplateMethodModel pv = (TemplateMethodModel) h.get("m" + mIdx);
+            final int expected = objIdx * 1000 + mIdx;
+            final int got = ((TemplateNumberModel) pv.exec(null)).getAsNumber().intValue();
+            if (got != expected) {
+                throw new AssertionError("Method assertation failed; " +
+                        "expected " + expected + ", but got " + got);
+            }
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/AlphabeticalMethodSorter.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/AlphabeticalMethodSorter.java b/src/test/java/org/apache/freemarker/core/model/impl/AlphabeticalMethodSorter.java
new file mode 100644
index 0000000..572bd39
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/AlphabeticalMethodSorter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.MethodDescriptor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+
+class AlphabeticalMethodSorter implements MethodSorter {
+
+    private final boolean desc;
+    
+    public AlphabeticalMethodSorter(boolean desc) {
+        this.desc = desc;
+    }
+
+    @Override
+    public MethodDescriptor[] sortMethodDescriptors(MethodDescriptor[] methodDescriptors) {
+        ArrayList<MethodDescriptor> ls = new ArrayList<>(Arrays.asList(methodDescriptors));
+        Collections.sort(ls, new Comparator<MethodDescriptor>() {
+            @Override
+            public int compare(MethodDescriptor o1, MethodDescriptor o2) {
+                int res = o1.getMethod().toString().compareTo(o2.getMethod().toString());
+                return desc ? -res : res;
+            }
+        });
+        return ls.toArray(new MethodDescriptor[ls.size()]);
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/CommonSupertypeForUnwrappingHintTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/CommonSupertypeForUnwrappingHintTest.java b/src/test/java/org/apache/freemarker/core/model/impl/CommonSupertypeForUnwrappingHintTest.java
new file mode 100644
index 0000000..89c488a
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/CommonSupertypeForUnwrappingHintTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+
+import junit.framework.TestCase;
+
+public class CommonSupertypeForUnwrappingHintTest extends TestCase {
+    
+    final OverloadedMethodsSubset oms = new DummyOverloadedMethodsSubset();
+
+    public CommonSupertypeForUnwrappingHintTest(String name) {
+        super(name);
+    }
+
+    public void testInterfaces() {
+        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(String.class, Number.class));
+        assertEquals(C1I1.class, oms.getCommonSupertypeForUnwrappingHint(C2ExtC1I1.class, C3ExtC1I1.class));
+        assertEquals(Object.class, oms.getCommonSupertypeForUnwrappingHint(C3I1I2.class, C4I1I2.class));
+        assertEquals(I1.class, oms.getCommonSupertypeForUnwrappingHint(C3I1I2.class, C5I1.class));
+        assertEquals(I1.class, oms.getCommonSupertypeForUnwrappingHint(C3I1I2.class, I1.class));
+        assertEquals(I2.class, oms.getCommonSupertypeForUnwrappingHint(C3I1I2.class, I2.class));
+        assertEquals(I1.class, oms.getCommonSupertypeForUnwrappingHint(I1I2.class, I1.class));
+        assertEquals(I2.class, oms.getCommonSupertypeForUnwrappingHint(I1I2.class, I2.class));
+        assertEquals(CharSequence.class, oms.getCommonSupertypeForUnwrappingHint(String.class, StringBuilder.class));
+        assertEquals(C6.class, oms.getCommonSupertypeForUnwrappingHint(C7ExtC6I1.class, C8ExtC6I1.class));
+    }
+
+    public void testArrayAndOther() {
+        testArrayAndOther(oms);
+    }
+    
+    /** These will be the same with oms and buggy: */
+    private void testArrayAndOther(OverloadedMethodsSubset oms) {
+        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(int[].class, String.class));
+        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(Object[].class, String.class));
+        
+        assertEquals(Object.class, oms.getCommonSupertypeForUnwrappingHint(int[].class, List.class));
+        assertEquals(Object.class, oms.getCommonSupertypeForUnwrappingHint(Object[].class, List.class));
+        
+        assertEquals(int[].class, oms.getCommonSupertypeForUnwrappingHint(int[].class, int[].class));
+        assertEquals(Object[].class, oms.getCommonSupertypeForUnwrappingHint(Object[].class, Object[].class));
+    }
+    
+    public void testArrayAndDifferentArray() {
+        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(int[].class, Object[].class));
+        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(int[].class, long[].class));
+    }
+    
+    public void testPrimitive() {
+        assertEquals(Integer.class, oms.getCommonSupertypeForUnwrappingHint(int.class, Integer.class));
+        assertEquals(Integer.class, oms.getCommonSupertypeForUnwrappingHint(Integer.class, int.class));
+        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(int.class, Long.class));
+        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(Long.class, int.class));
+        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(Integer.class, long.class));
+        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(long.class, Integer.class));
+        assertEquals(Boolean.class, oms.getCommonSupertypeForUnwrappingHint(boolean.class, Boolean.class));
+        assertEquals(Boolean.class, oms.getCommonSupertypeForUnwrappingHint(Boolean.class, boolean.class));
+        assertEquals(Character.class, oms.getCommonSupertypeForUnwrappingHint(char.class, Character.class));
+        assertEquals(Character.class, oms.getCommonSupertypeForUnwrappingHint(Character.class, char.class));
+        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(int.class, short.class));
+        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(short.class, int.class));
+    }
+
+    public void testMisc() {
+        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(Long.class, Integer.class));
+        assertEquals(char.class, oms.getCommonSupertypeForUnwrappingHint(char.class, char.class));
+        assertEquals(Integer.class, oms.getCommonSupertypeForUnwrappingHint(Integer.class, Integer.class));
+        assertEquals(String.class, oms.getCommonSupertypeForUnwrappingHint(String.class, String.class));
+    }
+    
+    static interface I1 { };
+    static class C1I1 implements I1 { };
+    static class C2ExtC1I1 extends C1I1 { };
+    static class C3ExtC1I1 extends C1I1 { };
+    static interface I2 { };
+    static class C3I1I2 implements I1, I2 { };
+    static class C4I1I2 implements I1, I2 { };
+    static class C5I1 implements I1 { };
+    static interface I1I2 extends I1, I2 { };
+    static class C6 { };
+    static class C7ExtC6I1 extends C6 implements I1 { };
+    static class C8ExtC6I1 extends C6 implements I1 { };
+    
+    private static class DummyOverloadedMethodsSubset extends OverloadedMethodsSubset {
+
+        DummyOverloadedMethodsSubset() {
+            super();
+        }
+
+        @Override
+        Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) {
+            return memberDesc.getParamTypes();
+        }
+
+        @Override
+        void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) {
+            // Do nothing
+        }
+
+        @Override
+        MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, DefaultObjectWrapper w) throws TemplateModelException {
+            throw new RuntimeException("Not implemented in this dummy.");
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..6946374
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperDesc.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class DefaultObjectWrapperDesc extends DefaultObjectWrapperWithSortedMethods {
+
+    public DefaultObjectWrapperDesc() {
+        super(Configuration.VERSION_3_0_0, true);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..ec7ce7c
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperInc.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class DefaultObjectWrapperInc extends DefaultObjectWrapperWithSortedMethods {
+
+    public DefaultObjectWrapperInc() {
+        super(Configuration.VERSION_3_0_0, false);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..62e3e5f
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperModelFactoryRegistrationTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import java.lang.ref.Reference;
+
+import org.apache.freemarker.core.Configuration;
+import org.junit.Test;
+
+public class DefaultObjectWrapperModelFactoryRegistrationTest {
+
+    @Test
+    public void introspectionSettingChanges() {
+        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+        ClassIntrospector ci1 = ow.getClassIntrospector();
+        checkRegisteredModelFactories(ci1, ow.getStaticModels(), ow.getEnumModels(), ow.getModelCache());
+
+        ow.setExposeFields(true);
+        ClassIntrospector ci2 = ow.getClassIntrospector();
+        assertNotSame(ci1, ci2);
+        checkRegisteredModelFactories(ci1);
+        checkRegisteredModelFactories(ci2, ow.getStaticModels(), ow.getEnumModels(), ow.getModelCache());
+
+        ow.setExposureLevel(DefaultObjectWrapper.EXPOSE_ALL);
+        ClassIntrospector ci3 = ow.getClassIntrospector();
+        assertNotSame(ci2, ci3);
+        checkRegisteredModelFactories(ci2);
+        checkRegisteredModelFactories(ci3, ow.getStaticModels(), ow.getEnumModels(), ow.getModelCache());
+
+        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.getModelCache());
+
+        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(), ow.getModelCache());
+    }
+
+    private void checkRegisteredModelFactories(ClassIntrospector ci, Object... expected) {
+        Object[] actualRefs = ci.getRegisteredModelFactoriesSnapshot();
+
+        scanActuals: for (Object actaulRef : actualRefs) {
+            Object actualItem = ((Reference) actaulRef).get();
+            for (Object expectedItem : expected) {
+                if (actualItem == expectedItem) {
+                    continue scanActuals;
+                }
+            }
+            fail("Actual item " + actualItem + " is not among the expected items");
+        }
+
+        scanExpecteds: for (Object expectedItem : expected) {
+            for (Object ref : actualRefs) {
+                Object actualItem = ((Reference) ref).get();
+                if (actualItem == expectedItem) {
+                    continue scanExpecteds;
+                }
+            }
+            fail("Expected item " + expectedItem + " is not among the actual items");
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..ab18f5d
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperReadOnlyTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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);
+                    }
+                }
+            }
+        }
+    }
+
+}


[06/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..9307a00
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Version;
+
+/**
+ * A hack to keep some unit tests producing exactly the same results on Java 8 as on Java 5-7, by hiding members
+ * added after Java 7.
+ */
+public class Java7MembersOnlyDefaultObjectWrapper extends DefaultObjectWrapper {
+    
+    private static final Set<String> POST_JAVA_7_MAP_METHODS = newHashSet(
+            "compute", "computeIfAbsent", "computeIfPresent",
+            "forEach", "getOrDefault", "merge", "putIfAbsent", "replace", "replaceAll");
+
+    private static final Set<String> POST_JAVA_7_ITERABLE_METHODS = newHashSet("forEach");
+    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() {
+
+        @Override
+        public void process(DecisionInput in, Decision out) {
+            Method m = in.getMethod();
+            Class declCl = m.getDeclaringClass();
+            if (Map.class.isAssignableFrom(declCl)) {
+                if (POST_JAVA_7_MAP_METHODS.contains(m.getName())) {
+                    hideMember(out);
+                    return;
+                }
+            }
+            if (Iterable.class.isAssignableFrom(declCl)) {
+                if (POST_JAVA_7_ITERABLE_METHODS.contains(m.getName())) {
+                    hideMember(out);
+                    return;
+                }
+            }
+            if (Collection.class.isAssignableFrom(declCl)) {
+                if (POST_JAVA_7_COLLECTION_METHODS.contains(m.getName())) {
+                    hideMember(out);
+                    return;
+                }
+            }
+            if (List.class.isAssignableFrom(declCl)) {
+                if (POST_JAVA_7_LIST_METHODS.contains(m.getName())) {
+                    hideMember(out);
+                    return;
+                }
+            }
+        }
+
+        private void hideMember(Decision out) {
+            out.setExposeMethodAs(null);
+            out.setExposeAsProperty(null);
+        }
+        
+    };
+    
+    public Java7MembersOnlyDefaultObjectWrapper(Version version) {
+        super(version);
+        setMethodAppearanceFineTuner(POST_JAVA_7_FILTER);
+    }
+
+    private static <T> Set<T> newHashSet(T... items) {
+        HashSet<T> r = new HashSet<>();
+        for (T item : items) {
+            r.add(item);
+        }
+        return r;
+    }
+
+    public Java7MembersOnlyDefaultObjectWrapper() {
+        this(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/ManyObjectsOfDifferentClasses.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/ManyObjectsOfDifferentClasses.java b/src/test/java/org/apache/freemarker/core/model/impl/ManyObjectsOfDifferentClasses.java
new file mode 100644
index 0000000..44ef2f5
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ManyObjectsOfDifferentClasses.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+// FMPP template source:
+// ----
+// package org.apache.freemarker.core.model.impl;
+// 
+// // FMPP template source:
+// // ----
+// <#assign src><#include "ManyObjectsOfDifferentClasses.java.ftl" parse=false></#assign>
+// ${src?trim?replace('(.*\r?\n)|.+', r'// $0', 'r')}
+// // ----
+// 
+// <#assign MAX_CLA = 8 - 1>
+// <#assign MAX_MET = 10 - 1>
+// <#assign MAX_PRO = 10 - 1>
+// 
+// public class ManyObjectsOfDifferentClasses {
+// 
+//     public static final Object[] OBJECTS = new Object[] {
+//         <#list 0..MAX_CLA as claI>
+//         new C${claI}(),
+//         </#list>
+//     };
+// 
+//     <#list 0..MAX_CLA as claI>
+//     static public class C${claI} {
+//         <#list 0..MAX_PRO as proI>
+//         public int getP${proI}() { return ${claI} * 1000 + ${proI}; }
+//         </#list>
+//         <#list 0..MAX_MET as metI>
+//         public int m${metI}() { return ${claI} * 1000 + ${metI}; };
+//         </#list>
+//     }
+//     </#list>
+//     
+// }
+// ----
+
+
+public class ManyObjectsOfDifferentClasses {
+
+    public static final Object[] OBJECTS = new Object[] {
+        new C0(),
+        new C1(),
+        new C2(),
+        new C3(),
+        new C4(),
+        new C5(),
+        new C6(),
+        new C7(),
+    };
+
+    static public class C0 {
+        public int getP0() { return 0 * 1000 + 0; }
+        public int getP1() { return 0 * 1000 + 1; }
+        public int getP2() { return 0 * 1000 + 2; }
+        public int getP3() { return 0 * 1000 + 3; }
+        public int getP4() { return 0 * 1000 + 4; }
+        public int getP5() { return 0 * 1000 + 5; }
+        public int getP6() { return 0 * 1000 + 6; }
+        public int getP7() { return 0 * 1000 + 7; }
+        public int getP8() { return 0 * 1000 + 8; }
+        public int getP9() { return 0 * 1000 + 9; }
+        public int m0() { return 0 * 1000 + 0; };
+        public int m1() { return 0 * 1000 + 1; };
+        public int m2() { return 0 * 1000 + 2; };
+        public int m3() { return 0 * 1000 + 3; };
+        public int m4() { return 0 * 1000 + 4; };
+        public int m5() { return 0 * 1000 + 5; };
+        public int m6() { return 0 * 1000 + 6; };
+        public int m7() { return 0 * 1000 + 7; };
+        public int m8() { return 0 * 1000 + 8; };
+        public int m9() { return 0 * 1000 + 9; };
+    }
+    static public class C1 {
+        public int getP0() { return 1 * 1000 + 0; }
+        public int getP1() { return 1 * 1000 + 1; }
+        public int getP2() { return 1 * 1000 + 2; }
+        public int getP3() { return 1 * 1000 + 3; }
+        public int getP4() { return 1 * 1000 + 4; }
+        public int getP5() { return 1 * 1000 + 5; }
+        public int getP6() { return 1 * 1000 + 6; }
+        public int getP7() { return 1 * 1000 + 7; }
+        public int getP8() { return 1 * 1000 + 8; }
+        public int getP9() { return 1 * 1000 + 9; }
+        public int m0() { return 1 * 1000 + 0; };
+        public int m1() { return 1 * 1000 + 1; };
+        public int m2() { return 1 * 1000 + 2; };
+        public int m3() { return 1 * 1000 + 3; };
+        public int m4() { return 1 * 1000 + 4; };
+        public int m5() { return 1 * 1000 + 5; };
+        public int m6() { return 1 * 1000 + 6; };
+        public int m7() { return 1 * 1000 + 7; };
+        public int m8() { return 1 * 1000 + 8; };
+        public int m9() { return 1 * 1000 + 9; };
+    }
+    static public class C2 {
+        public int getP0() { return 2 * 1000 + 0; }
+        public int getP1() { return 2 * 1000 + 1; }
+        public int getP2() { return 2 * 1000 + 2; }
+        public int getP3() { return 2 * 1000 + 3; }
+        public int getP4() { return 2 * 1000 + 4; }
+        public int getP5() { return 2 * 1000 + 5; }
+        public int getP6() { return 2 * 1000 + 6; }
+        public int getP7() { return 2 * 1000 + 7; }
+        public int getP8() { return 2 * 1000 + 8; }
+        public int getP9() { return 2 * 1000 + 9; }
+        public int m0() { return 2 * 1000 + 0; };
+        public int m1() { return 2 * 1000 + 1; };
+        public int m2() { return 2 * 1000 + 2; };
+        public int m3() { return 2 * 1000 + 3; };
+        public int m4() { return 2 * 1000 + 4; };
+        public int m5() { return 2 * 1000 + 5; };
+        public int m6() { return 2 * 1000 + 6; };
+        public int m7() { return 2 * 1000 + 7; };
+        public int m8() { return 2 * 1000 + 8; };
+        public int m9() { return 2 * 1000 + 9; };
+    }
+    static public class C3 {
+        public int getP0() { return 3 * 1000 + 0; }
+        public int getP1() { return 3 * 1000 + 1; }
+        public int getP2() { return 3 * 1000 + 2; }
+        public int getP3() { return 3 * 1000 + 3; }
+        public int getP4() { return 3 * 1000 + 4; }
+        public int getP5() { return 3 * 1000 + 5; }
+        public int getP6() { return 3 * 1000 + 6; }
+        public int getP7() { return 3 * 1000 + 7; }
+        public int getP8() { return 3 * 1000 + 8; }
+        public int getP9() { return 3 * 1000 + 9; }
+        public int m0() { return 3 * 1000 + 0; };
+        public int m1() { return 3 * 1000 + 1; };
+        public int m2() { return 3 * 1000 + 2; };
+        public int m3() { return 3 * 1000 + 3; };
+        public int m4() { return 3 * 1000 + 4; };
+        public int m5() { return 3 * 1000 + 5; };
+        public int m6() { return 3 * 1000 + 6; };
+        public int m7() { return 3 * 1000 + 7; };
+        public int m8() { return 3 * 1000 + 8; };
+        public int m9() { return 3 * 1000 + 9; };
+    }
+    static public class C4 {
+        public int getP0() { return 4 * 1000 + 0; }
+        public int getP1() { return 4 * 1000 + 1; }
+        public int getP2() { return 4 * 1000 + 2; }
+        public int getP3() { return 4 * 1000 + 3; }
+        public int getP4() { return 4 * 1000 + 4; }
+        public int getP5() { return 4 * 1000 + 5; }
+        public int getP6() { return 4 * 1000 + 6; }
+        public int getP7() { return 4 * 1000 + 7; }
+        public int getP8() { return 4 * 1000 + 8; }
+        public int getP9() { return 4 * 1000 + 9; }
+        public int m0() { return 4 * 1000 + 0; };
+        public int m1() { return 4 * 1000 + 1; };
+        public int m2() { return 4 * 1000 + 2; };
+        public int m3() { return 4 * 1000 + 3; };
+        public int m4() { return 4 * 1000 + 4; };
+        public int m5() { return 4 * 1000 + 5; };
+        public int m6() { return 4 * 1000 + 6; };
+        public int m7() { return 4 * 1000 + 7; };
+        public int m8() { return 4 * 1000 + 8; };
+        public int m9() { return 4 * 1000 + 9; };
+    }
+    static public class C5 {
+        public int getP0() { return 5 * 1000 + 0; }
+        public int getP1() { return 5 * 1000 + 1; }
+        public int getP2() { return 5 * 1000 + 2; }
+        public int getP3() { return 5 * 1000 + 3; }
+        public int getP4() { return 5 * 1000 + 4; }
+        public int getP5() { return 5 * 1000 + 5; }
+        public int getP6() { return 5 * 1000 + 6; }
+        public int getP7() { return 5 * 1000 + 7; }
+        public int getP8() { return 5 * 1000 + 8; }
+        public int getP9() { return 5 * 1000 + 9; }
+        public int m0() { return 5 * 1000 + 0; };
+        public int m1() { return 5 * 1000 + 1; };
+        public int m2() { return 5 * 1000 + 2; };
+        public int m3() { return 5 * 1000 + 3; };
+        public int m4() { return 5 * 1000 + 4; };
+        public int m5() { return 5 * 1000 + 5; };
+        public int m6() { return 5 * 1000 + 6; };
+        public int m7() { return 5 * 1000 + 7; };
+        public int m8() { return 5 * 1000 + 8; };
+        public int m9() { return 5 * 1000 + 9; };
+    }
+    static public class C6 {
+        public int getP0() { return 6 * 1000 + 0; }
+        public int getP1() { return 6 * 1000 + 1; }
+        public int getP2() { return 6 * 1000 + 2; }
+        public int getP3() { return 6 * 1000 + 3; }
+        public int getP4() { return 6 * 1000 + 4; }
+        public int getP5() { return 6 * 1000 + 5; }
+        public int getP6() { return 6 * 1000 + 6; }
+        public int getP7() { return 6 * 1000 + 7; }
+        public int getP8() { return 6 * 1000 + 8; }
+        public int getP9() { return 6 * 1000 + 9; }
+        public int m0() { return 6 * 1000 + 0; };
+        public int m1() { return 6 * 1000 + 1; };
+        public int m2() { return 6 * 1000 + 2; };
+        public int m3() { return 6 * 1000 + 3; };
+        public int m4() { return 6 * 1000 + 4; };
+        public int m5() { return 6 * 1000 + 5; };
+        public int m6() { return 6 * 1000 + 6; };
+        public int m7() { return 6 * 1000 + 7; };
+        public int m8() { return 6 * 1000 + 8; };
+        public int m9() { return 6 * 1000 + 9; };
+    }
+    static public class C7 {
+        public int getP0() { return 7 * 1000 + 0; }
+        public int getP1() { return 7 * 1000 + 1; }
+        public int getP2() { return 7 * 1000 + 2; }
+        public int getP3() { return 7 * 1000 + 3; }
+        public int getP4() { return 7 * 1000 + 4; }
+        public int getP5() { return 7 * 1000 + 5; }
+        public int getP6() { return 7 * 1000 + 6; }
+        public int getP7() { return 7 * 1000 + 7; }
+        public int getP8() { return 7 * 1000 + 8; }
+        public int getP9() { return 7 * 1000 + 9; }
+        public int m0() { return 7 * 1000 + 0; };
+        public int m1() { return 7 * 1000 + 1; };
+        public int m2() { return 7 * 1000 + 2; };
+        public int m3() { return 7 * 1000 + 3; };
+        public int m4() { return 7 * 1000 + 4; };
+        public int m5() { return 7 * 1000 + 5; };
+        public int m6() { return 7 * 1000 + 6; };
+        public int m7() { return 7 * 1000 + 7; };
+        public int m8() { return 7 * 1000 + 8; };
+        public int m9() { return 7 * 1000 + 9; };
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/ManyStaticsOfDifferentClasses.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/ManyStaticsOfDifferentClasses.java b/src/test/java/org/apache/freemarker/core/model/impl/ManyStaticsOfDifferentClasses.java
new file mode 100644
index 0000000..a3f6f36
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ManyStaticsOfDifferentClasses.java
@@ -0,0 +1,236 @@
+/*
+ * 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;
+
+// FMPP template source:
+// ----
+// package org.apache.freemarker.core.model.impl;
+// 
+// // FMPP template source:
+// // ----
+// <#assign src><#include "ManyStaticsOfDifferentClasses.java.ftl" parse=false></#assign>
+// ${src?trim?replace('(.*\r?\n)|.+', r'// $0', 'r')}
+// // ----
+// 
+// <#assign MAX_CLA = 8 - 1>
+// <#assign MAX_MET = 10 - 1>
+// <#assign MAX_PRO = 10 - 1>
+// 
+// public class ManyStaticsOfDifferentClasses {
+// 
+//     private ManyStaticsOfDifferentClasses() { }
+// 
+//     <#list 0..MAX_CLA as claI>
+//     static public class C${claI} {
+//         <#list 0..MAX_PRO as proI>
+//         public static int p${proI} = ${claI} * 1000 + ${proI};
+//         </#list>
+//         <#list 0..MAX_MET as metI>
+//         public static int m${metI}() { return ${claI} * 1000 + ${metI}; };
+//         </#list>
+//     }
+//     </#list>
+//     
+// }
+// ----
+
+
+public class ManyStaticsOfDifferentClasses {
+
+    private ManyStaticsOfDifferentClasses() { }
+
+    static public class C0 {
+        public static int p0 = 0 * 1000 + 0;
+        public static int p1 = 0 * 1000 + 1;
+        public static int p2 = 0 * 1000 + 2;
+        public static int p3 = 0 * 1000 + 3;
+        public static int p4 = 0 * 1000 + 4;
+        public static int p5 = 0 * 1000 + 5;
+        public static int p6 = 0 * 1000 + 6;
+        public static int p7 = 0 * 1000 + 7;
+        public static int p8 = 0 * 1000 + 8;
+        public static int p9 = 0 * 1000 + 9;
+        public static int m0() { return 0 * 1000 + 0; };
+        public static int m1() { return 0 * 1000 + 1; };
+        public static int m2() { return 0 * 1000 + 2; };
+        public static int m3() { return 0 * 1000 + 3; };
+        public static int m4() { return 0 * 1000 + 4; };
+        public static int m5() { return 0 * 1000 + 5; };
+        public static int m6() { return 0 * 1000 + 6; };
+        public static int m7() { return 0 * 1000 + 7; };
+        public static int m8() { return 0 * 1000 + 8; };
+        public static int m9() { return 0 * 1000 + 9; };
+    }
+    static public class C1 {
+        public static int p0 = 1 * 1000 + 0;
+        public static int p1 = 1 * 1000 + 1;
+        public static int p2 = 1 * 1000 + 2;
+        public static int p3 = 1 * 1000 + 3;
+        public static int p4 = 1 * 1000 + 4;
+        public static int p5 = 1 * 1000 + 5;
+        public static int p6 = 1 * 1000 + 6;
+        public static int p7 = 1 * 1000 + 7;
+        public static int p8 = 1 * 1000 + 8;
+        public static int p9 = 1 * 1000 + 9;
+        public static int m0() { return 1 * 1000 + 0; };
+        public static int m1() { return 1 * 1000 + 1; };
+        public static int m2() { return 1 * 1000 + 2; };
+        public static int m3() { return 1 * 1000 + 3; };
+        public static int m4() { return 1 * 1000 + 4; };
+        public static int m5() { return 1 * 1000 + 5; };
+        public static int m6() { return 1 * 1000 + 6; };
+        public static int m7() { return 1 * 1000 + 7; };
+        public static int m8() { return 1 * 1000 + 8; };
+        public static int m9() { return 1 * 1000 + 9; };
+    }
+    static public class C2 {
+        public static int p0 = 2 * 1000 + 0;
+        public static int p1 = 2 * 1000 + 1;
+        public static int p2 = 2 * 1000 + 2;
+        public static int p3 = 2 * 1000 + 3;
+        public static int p4 = 2 * 1000 + 4;
+        public static int p5 = 2 * 1000 + 5;
+        public static int p6 = 2 * 1000 + 6;
+        public static int p7 = 2 * 1000 + 7;
+        public static int p8 = 2 * 1000 + 8;
+        public static int p9 = 2 * 1000 + 9;
+        public static int m0() { return 2 * 1000 + 0; };
+        public static int m1() { return 2 * 1000 + 1; };
+        public static int m2() { return 2 * 1000 + 2; };
+        public static int m3() { return 2 * 1000 + 3; };
+        public static int m4() { return 2 * 1000 + 4; };
+        public static int m5() { return 2 * 1000 + 5; };
+        public static int m6() { return 2 * 1000 + 6; };
+        public static int m7() { return 2 * 1000 + 7; };
+        public static int m8() { return 2 * 1000 + 8; };
+        public static int m9() { return 2 * 1000 + 9; };
+    }
+    static public class C3 {
+        public static int p0 = 3 * 1000 + 0;
+        public static int p1 = 3 * 1000 + 1;
+        public static int p2 = 3 * 1000 + 2;
+        public static int p3 = 3 * 1000 + 3;
+        public static int p4 = 3 * 1000 + 4;
+        public static int p5 = 3 * 1000 + 5;
+        public static int p6 = 3 * 1000 + 6;
+        public static int p7 = 3 * 1000 + 7;
+        public static int p8 = 3 * 1000 + 8;
+        public static int p9 = 3 * 1000 + 9;
+        public static int m0() { return 3 * 1000 + 0; };
+        public static int m1() { return 3 * 1000 + 1; };
+        public static int m2() { return 3 * 1000 + 2; };
+        public static int m3() { return 3 * 1000 + 3; };
+        public static int m4() { return 3 * 1000 + 4; };
+        public static int m5() { return 3 * 1000 + 5; };
+        public static int m6() { return 3 * 1000 + 6; };
+        public static int m7() { return 3 * 1000 + 7; };
+        public static int m8() { return 3 * 1000 + 8; };
+        public static int m9() { return 3 * 1000 + 9; };
+    }
+    static public class C4 {
+        public static int p0 = 4 * 1000 + 0;
+        public static int p1 = 4 * 1000 + 1;
+        public static int p2 = 4 * 1000 + 2;
+        public static int p3 = 4 * 1000 + 3;
+        public static int p4 = 4 * 1000 + 4;
+        public static int p5 = 4 * 1000 + 5;
+        public static int p6 = 4 * 1000 + 6;
+        public static int p7 = 4 * 1000 + 7;
+        public static int p8 = 4 * 1000 + 8;
+        public static int p9 = 4 * 1000 + 9;
+        public static int m0() { return 4 * 1000 + 0; };
+        public static int m1() { return 4 * 1000 + 1; };
+        public static int m2() { return 4 * 1000 + 2; };
+        public static int m3() { return 4 * 1000 + 3; };
+        public static int m4() { return 4 * 1000 + 4; };
+        public static int m5() { return 4 * 1000 + 5; };
+        public static int m6() { return 4 * 1000 + 6; };
+        public static int m7() { return 4 * 1000 + 7; };
+        public static int m8() { return 4 * 1000 + 8; };
+        public static int m9() { return 4 * 1000 + 9; };
+    }
+    static public class C5 {
+        public static int p0 = 5 * 1000 + 0;
+        public static int p1 = 5 * 1000 + 1;
+        public static int p2 = 5 * 1000 + 2;
+        public static int p3 = 5 * 1000 + 3;
+        public static int p4 = 5 * 1000 + 4;
+        public static int p5 = 5 * 1000 + 5;
+        public static int p6 = 5 * 1000 + 6;
+        public static int p7 = 5 * 1000 + 7;
+        public static int p8 = 5 * 1000 + 8;
+        public static int p9 = 5 * 1000 + 9;
+        public static int m0() { return 5 * 1000 + 0; };
+        public static int m1() { return 5 * 1000 + 1; };
+        public static int m2() { return 5 * 1000 + 2; };
+        public static int m3() { return 5 * 1000 + 3; };
+        public static int m4() { return 5 * 1000 + 4; };
+        public static int m5() { return 5 * 1000 + 5; };
+        public static int m6() { return 5 * 1000 + 6; };
+        public static int m7() { return 5 * 1000 + 7; };
+        public static int m8() { return 5 * 1000 + 8; };
+        public static int m9() { return 5 * 1000 + 9; };
+    }
+    static public class C6 {
+        public static int p0 = 6 * 1000 + 0;
+        public static int p1 = 6 * 1000 + 1;
+        public static int p2 = 6 * 1000 + 2;
+        public static int p3 = 6 * 1000 + 3;
+        public static int p4 = 6 * 1000 + 4;
+        public static int p5 = 6 * 1000 + 5;
+        public static int p6 = 6 * 1000 + 6;
+        public static int p7 = 6 * 1000 + 7;
+        public static int p8 = 6 * 1000 + 8;
+        public static int p9 = 6 * 1000 + 9;
+        public static int m0() { return 6 * 1000 + 0; };
+        public static int m1() { return 6 * 1000 + 1; };
+        public static int m2() { return 6 * 1000 + 2; };
+        public static int m3() { return 6 * 1000 + 3; };
+        public static int m4() { return 6 * 1000 + 4; };
+        public static int m5() { return 6 * 1000 + 5; };
+        public static int m6() { return 6 * 1000 + 6; };
+        public static int m7() { return 6 * 1000 + 7; };
+        public static int m8() { return 6 * 1000 + 8; };
+        public static int m9() { return 6 * 1000 + 9; };
+    }
+    static public class C7 {
+        public static int p0 = 7 * 1000 + 0;
+        public static int p1 = 7 * 1000 + 1;
+        public static int p2 = 7 * 1000 + 2;
+        public static int p3 = 7 * 1000 + 3;
+        public static int p4 = 7 * 1000 + 4;
+        public static int p5 = 7 * 1000 + 5;
+        public static int p6 = 7 * 1000 + 6;
+        public static int p7 = 7 * 1000 + 7;
+        public static int p8 = 7 * 1000 + 8;
+        public static int p9 = 7 * 1000 + 9;
+        public static int m0() { return 7 * 1000 + 0; };
+        public static int m1() { return 7 * 1000 + 1; };
+        public static int m2() { return 7 * 1000 + 2; };
+        public static int m3() { return 7 * 1000 + 3; };
+        public static int m4() { return 7 * 1000 + 4; };
+        public static int m5() { return 7 * 1000 + 5; };
+        public static int m6() { return 7 * 1000 + 6; };
+        public static int m7() { return 7 * 1000 + 7; };
+        public static int m8() { return 7 * 1000 + 8; };
+        public static int m9() { return 7 * 1000 + 9; };
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/MiscNumericalOperationsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/MiscNumericalOperationsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/MiscNumericalOperationsTest.java
new file mode 100644
index 0000000..148211e
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/MiscNumericalOperationsTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.junit.Assert;
+
+import junit.framework.TestCase;
+
+public class MiscNumericalOperationsTest extends TestCase {
+
+    public MiscNumericalOperationsTest(String name) {
+        super(name);
+    }
+    
+    public void testForceUnwrappedNumberToType() {
+        // Usual type to to all other types:
+        Double n = Double.valueOf(123.75);
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Short.class), Short.valueOf(n.shortValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Short.TYPE), Short.valueOf(n.shortValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Byte.class), Byte.valueOf(n.byteValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Byte.TYPE), Byte.valueOf(n.byteValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Integer.class), Integer.valueOf(n.intValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Integer.TYPE), Integer.valueOf(n.intValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Long.class), Long.valueOf(n.longValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Long.TYPE), Long.valueOf(n.longValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Float.class), Float.valueOf(n.floatValue()));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Float.TYPE), Float.valueOf(n.floatValue()));
+        assertTrue(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Double.class) == n);
+        assertTrue(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Double.TYPE) == n);
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, BigInteger.class), new BigInteger("123"));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(n, BigDecimal.class), new BigDecimal("123.75"));
+        
+        // Cases of conversion to BigDecimal:
+        BigDecimal bd = new BigDecimal("123");
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(new BigInteger("123"), BigDecimal.class), bd);
+        assertTrue(DefaultObjectWrapper.forceUnwrappedNumberToType(bd, BigDecimal.class) == bd);
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(Long.valueOf(123), BigDecimal.class), bd);
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(Double.valueOf(123), BigDecimal.class), bd);
+        
+        // Cases of conversion to BigInteger:
+        BigInteger bi = new BigInteger("123");
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(new BigDecimal("123.6"), BigInteger.class), bi);
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(
+                new OverloadedNumberUtil.IntegerBigDecimal(new BigDecimal("123")), BigInteger.class), bi);
+        assertTrue(DefaultObjectWrapper.forceUnwrappedNumberToType(bi, BigInteger.class) == bi);
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(Long.valueOf(123), BigInteger.class), bi);
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(Double.valueOf(123.6), BigInteger.class), bi);
+
+        assertTrue(DefaultObjectWrapper.forceUnwrappedNumberToType(n, Number.class) == n);
+        assertNull(DefaultObjectWrapper.forceUnwrappedNumberToType(n, RationalNumber.class));
+        RationalNumber r = new RationalNumber(1, 2);
+        assertTrue(DefaultObjectWrapper.forceUnwrappedNumberToType(r, RationalNumber.class) == r);
+        assertTrue(DefaultObjectWrapper.forceUnwrappedNumberToType(r, Number.class) == r);
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(r, Double.class), Double.valueOf(0.5));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(r, BigDecimal.class), new BigDecimal("0.5"));
+        assertEquals(DefaultObjectWrapper.forceUnwrappedNumberToType(r, BigInteger.class), BigInteger.ZERO);
+    }
+    
+    @SuppressWarnings("boxing")
+    public void testForceNumberArgumentsToParameterTypes() {
+        OverloadedMethodsSubset oms = new OverloadedFixArgsMethods();
+        Class[] paramTypes = new Class[] { Short.TYPE, Short.class, Double.TYPE, BigDecimal.class, BigInteger.class };
+        Object[] args;
+        
+        args = newArgs();
+        oms.forceNumberArgumentsToParameterTypes(args, paramTypes, new int[] { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF });
+        Assert.assertArrayEquals(
+                args,
+                new Object[] { (short) 123, (short) 123, 123.75, new BigDecimal("123.75"), BigInteger.valueOf(123) });
+        
+        args = newArgs();
+        oms.forceNumberArgumentsToParameterTypes(args, paramTypes, new int[] { 0, 0, 0, 0, 0 });
+        Assert.assertArrayEquals(args, newArgs());
+        
+        args = newArgs();
+        oms.forceNumberArgumentsToParameterTypes(args, paramTypes, new int[] { 8, 8, 8, 8, 8 });
+        Assert.assertArrayEquals(args, newArgs());
+        
+        args = newArgs();
+        oms.forceNumberArgumentsToParameterTypes(args, paramTypes, new int[] { 0xFFFF, 0, 0xFFFF, 0, 0xFFFF });
+        Assert.assertArrayEquals(
+                args,
+                new Object[] { (short) 123, 123.75, 123.75, 123.75, BigInteger.valueOf(123) });
+    }
+
+    @SuppressWarnings("boxing")
+    private Object[] newArgs() {
+        return new Object[] { 123.75, 123.75, 123.75, 123.75, 123.75 };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..7e66b72
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.test.util.TestUtil;
+
+import junit.framework.TestCase;
+
+public class ModelAPINewInstanceTest extends TestCase {
+
+    private DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+
+    public ModelAPINewInstanceTest(String name) {
+        super(name);
+    }
+    
+    @SuppressWarnings("boxing")
+    public void test() throws Exception {
+        testClassConstructors("");
+        testClassConstructors("int... [1]", 1);
+        testClassConstructors("int 1, int 2", 1, 2);
+        testClassConstructors("int... [1, 2, 3]", 1, 2, 3);
+        testClassConstructors("int... [1, 2, 3]", 1, (byte) 2, (short) 3);
+        try {
+            testClassConstructors("int... [1, 2, 3]", 1, 2, (long) 3);
+            fail();
+        } catch (NoSuchMethodException e) {
+            // Expected
+        }
+        
+        testClassConstructors("int 1, int 2", (byte) 1, (short) 2);
+        testClassConstructors("double 1.0, double 2.0", (long) 1, (short) 2);
+        testClassConstructors("double 1.0, double 2.0", 1, 2f);
+        testClassConstructors("Integer null, Integer null", null, null);
+        testClassConstructors("Integer null, Integer 1", null, 1);
+        testClassConstructors("int 1, String s", 1, "s");
+        testClassConstructors("int 1, String null", 1, null);
+        testClassConstructors("Object null, Object s", null, "s");
+        testClassConstructors("Object 1.0, Object s", 1f, "s");
+        
+        testClassConstructors("Object s, int... [1, 2]", "s", 1, 2);
+        testClassConstructors("Object s, int... []", "s");
+        
+        testClassConstructors2("int 1, int 2", (byte) 1, (short) 2);
+        try {
+            testClassConstructors2("int 1, int 2", 1, 2L);
+            fail();
+        } catch (NoSuchMethodException e) {
+            // Expected
+        }
+        try {
+            testClassConstructors2("", "", "");
+            fail();
+        } catch (NoSuchMethodException e) {
+            // Expected
+        }
+        try {
+            testClassConstructors2("int 1", 1);
+            fail();
+        } catch (NoSuchMethodException e) {
+            // Expected
+        }
+        try {
+            testClassConstructors2("");
+            fail();
+        } catch (NoSuchMethodException e) {
+            // Expected
+        }
+    }
+    
+    private void testClassConstructors(String expected, Object... args) throws Exception {
+        testCall(expected, Constructors.class, args); 
+    }
+
+    private void testClassConstructors2(String expected, Object... args) throws Exception {
+        testCall(expected, Constructors2.class, args); 
+    }
+    
+    private void testCall(String expected, Class cl, Object... args) throws Exception {
+        Object obj = _ModelAPI.newInstance(cl, args, ow);
+        assertEquals(expected, obj.toString());        
+    }
+    
+    public static class Constructors {
+        private final String s;
+
+        public Constructors() { s = ""; }
+        
+        public Constructors(int x, int y) { s = "int " + x + ", int " + y; }
+        public Constructors(int x, String y) { s = "int " + x + ", String " + y; }
+        public Constructors(int x, long y) { s = "int " + x + ", long " + y; }
+        public Constructors(double x, double y) { s = "double " + x + ", double " + y; }
+        public Constructors(Integer x, Integer y) { s = "Integer " + x + ", Integer " + y; }
+        public Constructors(Object x, Object y) { s = "Object " + x + ", Object " + y; }
+
+        public Constructors(int... xs) { s = "int... " + TestUtil.arrayToString(xs); }
+        public Constructors(Object x, int... ys) { s = "Object " + x + ", int... " + TestUtil.arrayToString(ys); }
+        
+        @Override
+        public String toString() {
+            return s;
+        }
+    }
+
+    public static class Constructors2 {
+        private final String s;
+
+        public Constructors2(int x, int y) { s = "int " + x + ", int " + y; }
+        
+        @Override
+        public String toString() {
+            return s;
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..4ad3c0e
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import static org.junit.Assert.*;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ModelCacheTest {
+    
+    @Test
+    public void modelCacheOff() throws Exception {
+        DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        assertFalse(ow.getUseModelCache());  // default is off
+        
+        String s = "foo";
+        assertNotSame(ow.wrap(s), ow.wrap(s));
+        
+        C c = new C();
+        assertNotSame(ow.wrap(c), ow.wrap(c));
+    }
+    
+    @Test
+    public void modelCacheOn() throws Exception {
+        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+        ow.setUseModelCache(true);
+        assertTrue(ow.getUseModelCache());
+
+        TestBean obj = new TestBean();
+        assertSame(ow.wrap(obj), ow.wrap(obj));
+        
+        C c = new C();
+        TemplateModel wrappedC = ow.wrap(c);
+        assertSame(wrappedC, ow.wrap(c));
+        
+        ow.clearClassIntrospecitonCache();
+        assertNotSame(wrappedC, ow.wrap(c));
+        assertSame(ow.wrap(c), ow.wrap(c));
+    }
+
+    static public class C { }
+
+    public static class TestBean {
+        //
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtilTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtilTest.java b/src/test/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtilTest.java
new file mode 100644
index 0000000..646da00
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtilTest.java
@@ -0,0 +1,585 @@
+/*
+ * 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.math.BigDecimal;
+import java.math.BigInteger;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("boxing")
+public class OverloadedNumberUtilTest extends TestCase {
+
+    public OverloadedNumberUtilTest(String name) {
+        super(name);
+    }
+    
+    public void testIntegerCoercions() {
+        cipEqu(Byte.valueOf(Byte.MAX_VALUE));
+        cipEqu(Byte.valueOf((byte) 0));
+        cipEqu(Byte.valueOf(Byte.MIN_VALUE));
+        
+        cipEqu(Short.valueOf(Byte.MAX_VALUE),
+                new OverloadedNumberUtil.ShortOrByte((short) Byte.MAX_VALUE, Byte.MAX_VALUE));
+        cipEqu(Short.valueOf((short) 0),
+                new OverloadedNumberUtil.ShortOrByte((short) 0, (byte) 0));
+        cipEqu(Short.valueOf(Byte.MIN_VALUE),
+                new OverloadedNumberUtil.ShortOrByte((short) Byte.MIN_VALUE, Byte.MIN_VALUE));
+        
+        cipEqu(Short.valueOf((short) (Byte.MAX_VALUE + 1)));
+        cipEqu(Short.valueOf((short) (Byte.MIN_VALUE - 1)));
+        cipEqu(Short.valueOf(Short.MAX_VALUE));
+        cipEqu(Short.valueOf(Short.MIN_VALUE));
+        
+        cipEqu(Integer.valueOf(Byte.MAX_VALUE),
+                new OverloadedNumberUtil.IntegerOrByte((int) Byte.MAX_VALUE, Byte.MAX_VALUE));
+        cipEqu(Integer.valueOf(0),
+                new OverloadedNumberUtil.IntegerOrByte(0, (byte) 0));
+        cipEqu(Integer.valueOf(Byte.MIN_VALUE),
+                new OverloadedNumberUtil.IntegerOrByte((int) Byte.MIN_VALUE, Byte.MIN_VALUE));
+        
+        cipEqu(Integer.valueOf(Byte.MAX_VALUE + 1),
+                new OverloadedNumberUtil.IntegerOrShort(Byte.MAX_VALUE + 1, (short) (Byte.MAX_VALUE + 1)));
+        cipEqu(Integer.valueOf(Byte.MIN_VALUE - 1),
+                new OverloadedNumberUtil.IntegerOrShort(Byte.MIN_VALUE - 1, (short) (Byte.MIN_VALUE - 1)));
+        cipEqu(Integer.valueOf(Short.MAX_VALUE),
+                new OverloadedNumberUtil.IntegerOrShort((int) Short.MAX_VALUE, Short.MAX_VALUE));
+        cipEqu(Integer.valueOf(Short.MIN_VALUE),
+                new OverloadedNumberUtil.IntegerOrShort((int) Short.MIN_VALUE, Short.MIN_VALUE));
+        
+        cipEqu(Integer.valueOf(Short.MAX_VALUE + 1));
+        cipEqu(Integer.valueOf(Short.MIN_VALUE - 1));
+        cipEqu(Integer.valueOf(Integer.MAX_VALUE));
+        cipEqu(Integer.valueOf(Integer.MIN_VALUE));
+        
+        cipEqu(Long.valueOf(Byte.MAX_VALUE),
+                new OverloadedNumberUtil.LongOrByte((long) Byte.MAX_VALUE, Byte.MAX_VALUE));
+        cipEqu(Long.valueOf(0),
+                new OverloadedNumberUtil.LongOrByte((long) 0, (byte) 0));
+        cipEqu(Long.valueOf(Byte.MIN_VALUE),
+                new OverloadedNumberUtil.LongOrByte((long) Byte.MIN_VALUE, Byte.MIN_VALUE));
+        
+        cipEqu(Long.valueOf(Byte.MAX_VALUE + 1),
+                new OverloadedNumberUtil.LongOrShort((long) (Byte.MAX_VALUE + 1), (short) (Byte.MAX_VALUE + 1)));
+        cipEqu(Long.valueOf(Byte.MIN_VALUE - 1),
+                new OverloadedNumberUtil.LongOrShort((long) (Byte.MIN_VALUE - 1), (short) (Byte.MIN_VALUE - 1)));
+        cipEqu(Long.valueOf(Short.MAX_VALUE),
+                new OverloadedNumberUtil.LongOrShort((long) Short.MAX_VALUE, Short.MAX_VALUE));
+        cipEqu(Long.valueOf(Short.MIN_VALUE),
+                new OverloadedNumberUtil.LongOrShort((long) Short.MIN_VALUE, Short.MIN_VALUE));
+
+        cipEqu(Long.valueOf(Short.MAX_VALUE + 1),
+                new OverloadedNumberUtil.LongOrInteger((long) Short.MAX_VALUE + 1, Short.MAX_VALUE + 1));
+        cipEqu(Long.valueOf(Short.MIN_VALUE - 1),
+                new OverloadedNumberUtil.LongOrInteger((long) Short.MIN_VALUE - 1, Short.MIN_VALUE - 1));
+        cipEqu(Long.valueOf(Integer.MAX_VALUE),
+                new OverloadedNumberUtil.LongOrInteger((long) Integer.MAX_VALUE, Integer.MAX_VALUE));
+        cipEqu(Long.valueOf(Integer.MIN_VALUE),
+                new OverloadedNumberUtil.LongOrInteger((long) Integer.MIN_VALUE, Integer.MIN_VALUE));
+        
+        cipEqu(Long.valueOf(Integer.MAX_VALUE + 1L));
+        cipEqu(Long.valueOf(Integer.MIN_VALUE - 1L));
+        cipEqu(Long.valueOf(Long.MAX_VALUE));
+        cipEqu(Long.valueOf(Long.MIN_VALUE));
+    }
+    
+    public void testIntegerNoCoercions() {
+        cipEqu(Integer.valueOf(Byte.MAX_VALUE), Integer.valueOf(Byte.MAX_VALUE), 0);
+        cipEqu(Integer.valueOf(0), Integer.valueOf(0), 0);
+        cipEqu(Integer.valueOf(Byte.MIN_VALUE), Integer.valueOf(Byte.MIN_VALUE), 0);
+    }
+    
+    public void testIntegerLimitedCoercions() {
+        cipEqu(Integer.valueOf(Byte.MAX_VALUE), Integer.valueOf(Byte.MAX_VALUE), TypeFlags.INTEGER);
+        cipEqu(Integer.valueOf(0), Integer.valueOf(0), TypeFlags.INTEGER);
+        cipEqu(Integer.valueOf(Byte.MIN_VALUE), Integer.valueOf(Byte.MIN_VALUE), TypeFlags.INTEGER);
+        
+        cipEqu(Long.valueOf(Integer.MAX_VALUE + 1L), Long.valueOf(Integer.MAX_VALUE + 1L), TypeFlags.INTEGER);
+        
+        for (int n = -1; n < 2; n++) {
+            final Long longN = Long.valueOf(n);
+            cipEqu(longN, new OverloadedNumberUtil.LongOrInteger(longN, n), TypeFlags.INTEGER);
+            cipEqu(longN, new OverloadedNumberUtil.LongOrShort(longN, (short) n), TypeFlags.SHORT);
+            cipEqu(longN, new OverloadedNumberUtil.LongOrByte(longN, (byte) n), TypeFlags.BYTE);
+            cipEqu(longN, new OverloadedNumberUtil.LongOrShort(longN, (short) n),
+                    TypeFlags.SHORT | TypeFlags.INTEGER);
+        }
+    }
+
+    public void testBigDecimalCoercions() {
+        cipEqu(new BigDecimal(123), new OverloadedNumberUtil.IntegerBigDecimal(new BigDecimal(123)));
+        cipEqu(new BigDecimal(123), new OverloadedNumberUtil.IntegerBigDecimal(new BigDecimal(123)),
+                TypeFlags.DOUBLE | TypeFlags.INTEGER);
+        cipEqu(new BigDecimal(123), TypeFlags.INTEGER);
+        cipEqu(new BigDecimal(123), TypeFlags.INTEGER | TypeFlags.LONG);
+        cipEqu(new BigDecimal(123), TypeFlags.DOUBLE);
+        cipEqu(new BigDecimal(123), TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        
+        cipEqu(new BigDecimal(123.5));
+        // Not wasting time with check if it's a whole number if we only have integer-only or non-integer-only targets:  
+        cipEqu(new BigDecimal(123.5), TypeFlags.INTEGER | TypeFlags.LONG);
+        cipEqu(new BigDecimal(123.5), TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        
+        cipEqu(new BigDecimal("0.01"));
+        cipEqu(new BigDecimal("-0.01"));
+        cipEqu(BigDecimal.ZERO, new OverloadedNumberUtil.IntegerBigDecimal(BigDecimal.ZERO));
+    }
+
+    public void testUnknownNumberCoercion() {
+        cipEqu(new RationalNumber(2, 3));
+    }
+
+    @SuppressWarnings("boxing")
+    public void testDoubleCoercion() {
+        cipEqu(Double.valueOf(1.5), new OverloadedNumberUtil.DoubleOrFloat(1.5));
+        cipEqu(Double.valueOf(-0.125), new OverloadedNumberUtil.DoubleOrFloat(-0.125));
+        cipEqu(Double.valueOf(Float.MAX_VALUE), new OverloadedNumberUtil.DoubleOrFloat((double) Float.MAX_VALUE));
+        cipEqu(Double.valueOf(-Float.MAX_VALUE), new OverloadedNumberUtil.DoubleOrFloat((double) -Float.MAX_VALUE));
+        cipEqu(Double.valueOf(Float.MAX_VALUE * 10.0));
+        cipEqu(Double.valueOf(-Float.MAX_VALUE * 10.0));
+        
+        cipEqu(Double.valueOf(0), new OverloadedNumberUtil.DoubleOrByte(0.0, (byte) 0));
+        cipEqu(Double.valueOf(Byte.MAX_VALUE), new OverloadedNumberUtil.DoubleOrByte((double) Byte.MAX_VALUE, Byte.MAX_VALUE));
+        cipEqu(Double.valueOf(Byte.MIN_VALUE), new OverloadedNumberUtil.DoubleOrByte((double) Byte.MIN_VALUE, Byte.MIN_VALUE));
+        
+        cipEqu(Double.valueOf(Byte.MAX_VALUE + 1), new OverloadedNumberUtil.DoubleOrShort((double)
+                (Byte.MAX_VALUE + 1), (short) (Byte.MAX_VALUE + 1)));
+        cipEqu(Double.valueOf(Byte.MIN_VALUE - 1), new OverloadedNumberUtil.DoubleOrShort((double)
+                (Byte.MIN_VALUE - 1), (short) (Byte.MIN_VALUE - 1)));
+        
+        cipEqu(Double.valueOf(Short.MAX_VALUE + 1),
+                new OverloadedNumberUtil.DoubleOrIntegerOrFloat((double) Short.MAX_VALUE + 1, Short.MAX_VALUE + 1));
+        cipEqu(Double.valueOf(Short.MIN_VALUE - 1),
+                new OverloadedNumberUtil.DoubleOrIntegerOrFloat((double) Short.MIN_VALUE - 1, Short.MIN_VALUE -  1));
+        cipEqu(Double.valueOf(16777216), new OverloadedNumberUtil.DoubleOrIntegerOrFloat(16777216.0, 16777216));
+        cipEqu(Double.valueOf(-16777216), new OverloadedNumberUtil.DoubleOrIntegerOrFloat(-16777216.0, -16777216));
+        
+        cipEqu(Double.valueOf(Integer.MAX_VALUE),
+                new OverloadedNumberUtil.DoubleOrInteger((double) Integer.MAX_VALUE, Integer.MAX_VALUE));
+        cipEqu(Double.valueOf(Integer.MIN_VALUE),
+                new OverloadedNumberUtil.DoubleOrInteger((double) Integer.MIN_VALUE, Integer.MIN_VALUE));
+        
+        cipEqu(Double.valueOf(Integer.MAX_VALUE + 1L),
+                new OverloadedNumberUtil.DoubleOrLong(Double.valueOf(Integer.MAX_VALUE + 1L), Integer.MAX_VALUE + 1L));
+        cipEqu(Double.valueOf(Integer.MIN_VALUE - 1L),
+                new OverloadedNumberUtil.DoubleOrLong(Double.valueOf(Integer.MIN_VALUE - 1L), Integer.MIN_VALUE - 1L));
+        cipEqu(Double.valueOf(Long.MAX_VALUE),
+                new OverloadedNumberUtil.DoubleOrFloat((double) Long.MAX_VALUE));
+        cipEqu(Double.valueOf(Long.MIN_VALUE),
+                new OverloadedNumberUtil.DoubleOrFloat((double) Long.MIN_VALUE));
+
+        // When only certain target types are present:
+        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrByte(5.0, (byte) 5), TypeFlags.BYTE);
+        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrByte(5.0, (byte) 5), TypeFlags.BYTE | TypeFlags.SHORT);
+        cipEqu(Double.valueOf(-129), TypeFlags.BYTE);
+        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrShort(5.0, (short) 5), TypeFlags.SHORT);
+        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrInteger(5.0, 5), TypeFlags.INTEGER);
+        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrLong(5.0, 5), TypeFlags.LONG);
+        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrFloat(5.0), TypeFlags.FLOAT);
+        cipEqu(Double.valueOf(5), Double.valueOf(5), TypeFlags.DOUBLE);
+        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrFloat(5.0),
+                TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(Double.valueOf(5.9), new OverloadedNumberUtil.DoubleOrFloat(5.9),
+                TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(Double.valueOf(5.9),
+                TypeFlags.DOUBLE | TypeFlags.INTEGER);
+        cipEqu(Double.valueOf(5.9), new OverloadedNumberUtil.DoubleOrFloat(5.9),
+                TypeFlags.FLOAT | TypeFlags.INTEGER);
+        cipEqu(Double.valueOf(5.9), TypeFlags.INTEGER);
+        cipEqu(Double.valueOf(Long.MAX_VALUE),
+                new OverloadedNumberUtil.DoubleOrFloat((double) Long.MAX_VALUE),
+                TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(Double.valueOf(Long.MAX_VALUE),
+                TypeFlags.DOUBLE | TypeFlags.LONG);
+        cipEqu(Double.valueOf(Long.MIN_VALUE),
+                new OverloadedNumberUtil.DoubleOrFloat((double) Long.MIN_VALUE),
+                TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(Double.valueOf(Float.MAX_VALUE * 10),
+                TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(Double.valueOf(-Float.MAX_VALUE * 10),
+                TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        
+        // Rounded values:
+        cipEqu(Double.valueOf(0.0000009),
+                new OverloadedNumberUtil.DoubleOrByte(0.0000009, (byte) 0));
+        cipEqu(Double.valueOf(-0.0000009),
+                new OverloadedNumberUtil.DoubleOrByte(-0.0000009, (byte) 0));
+        cipEqu(Double.valueOf(0.9999991),
+                new OverloadedNumberUtil.DoubleOrByte(0.9999991, (byte) 1));
+        cipEqu(Double.valueOf(-0.9999991),
+                new OverloadedNumberUtil.DoubleOrByte(-0.9999991, (byte) -1));
+        cipEqu(Double.valueOf(0.0000009),
+                new OverloadedNumberUtil.DoubleOrShort(0.0000009, (short) 0),
+                TypeFlags.SHORT | TypeFlags.DOUBLE);
+        cipEqu(Double.valueOf(0.0000009), new OverloadedNumberUtil.DoubleOrInteger(0.0000009, 0),
+                TypeFlags.INTEGER | TypeFlags.DOUBLE);
+        cipEqu(Double.valueOf(0.0000009), new OverloadedNumberUtil.DoubleOrLong(0.0000009, 0),
+                TypeFlags.LONG | TypeFlags.DOUBLE);
+        cipEqu(Double.valueOf(0.0000009),
+                new OverloadedNumberUtil.DoubleOrByte(0.0000009, (byte) 0), TypeFlags.BYTE);
+        cipEqu(Double.valueOf(0.0000009),
+                new OverloadedNumberUtil.DoubleOrShort(0.0000009, (short) 0), TypeFlags.SHORT);
+        cipEqu(Double.valueOf(0.0000009),
+                new OverloadedNumberUtil.DoubleOrInteger(0.0000009, 0), TypeFlags.INTEGER);
+        cipEqu(Double.valueOf(0.0000009),
+                new OverloadedNumberUtil.DoubleOrLong(0.0000009, 0L), TypeFlags.LONG);
+        cipEqu(Double.valueOf(0.9999999),
+                new OverloadedNumberUtil.DoubleOrInteger(0.9999999, 1), TypeFlags.INTEGER);
+        cipEqu(Double.valueOf(Byte.MAX_VALUE + 0.9e-6),
+                new OverloadedNumberUtil.DoubleOrByte(Byte.MAX_VALUE + 0.9e-6, Byte.MAX_VALUE));
+        cipEqu(Double.valueOf(Byte.MIN_VALUE - 0.9e-6),
+                new OverloadedNumberUtil.DoubleOrByte(Byte.MIN_VALUE - 0.9e-6, Byte.MIN_VALUE));
+        cipEqu(Double.valueOf(Byte.MAX_VALUE + 1.1e-6),
+                new OverloadedNumberUtil.DoubleOrFloat(Byte.MAX_VALUE + 1.1e-6));
+        cipEqu(Double.valueOf(Byte.MIN_VALUE - 1.1e-6),
+                new OverloadedNumberUtil.DoubleOrFloat(Byte.MIN_VALUE - 1.1e-6));
+        cipEqu(Double.valueOf(Byte.MAX_VALUE + 0.9999991),
+                new OverloadedNumberUtil.DoubleOrShort(
+                        Byte.MAX_VALUE + 0.9999991, (short) (Byte.MAX_VALUE + 1)));
+        cipEqu(Double.valueOf(Byte.MIN_VALUE - 0.9999991),
+                new OverloadedNumberUtil.DoubleOrShort(
+                        Byte.MIN_VALUE - 0.9999991, (short) (Byte.MIN_VALUE - 1)));
+        
+        cipEqu(Double.valueOf(Byte.MAX_VALUE + 1), new OverloadedNumberUtil.DoubleOrShort((double)
+                (Byte.MAX_VALUE + 1), (short) (Byte.MAX_VALUE + 1)));
+        cipEqu(Double.valueOf(Byte.MIN_VALUE - 1), new OverloadedNumberUtil.DoubleOrShort((double)
+                (Byte.MIN_VALUE - 1), (short) (Byte.MIN_VALUE - 1)));
+        
+        cipEqu(Short.MAX_VALUE + 0.9999991,
+                new OverloadedNumberUtil.DoubleOrIntegerOrFloat(Short.MAX_VALUE + 0.9999991, Short.MAX_VALUE + 1));
+        cipEqu(Short.MIN_VALUE - 0.9999991,
+                new OverloadedNumberUtil.DoubleOrIntegerOrFloat(Short.MIN_VALUE - 0.9999991, Short.MIN_VALUE - 1));
+        cipEqu(16777216 + 0.9e-6,
+                new OverloadedNumberUtil.DoubleOrIntegerOrFloat(16777216 + 0.9e-6, 16777216));
+        cipEqu(-16777216 - 0.9e-6,
+                new OverloadedNumberUtil.DoubleOrIntegerOrFloat(-16777216 - 0.9e-6, -16777216));
+        
+        cipEqu(Integer.MAX_VALUE + 0.9e-6,
+                new OverloadedNumberUtil.DoubleOrInteger(Integer.MAX_VALUE + 0.9e-6, Integer.MAX_VALUE));
+        cipEqu(Integer.MIN_VALUE - 0.9e-6,
+                new OverloadedNumberUtil.DoubleOrInteger(Integer.MIN_VALUE - 0.9e-6, Integer.MIN_VALUE));
+        
+        cipEqu(Integer.MAX_VALUE + 1L + 0.9e-6,
+                new OverloadedNumberUtil.DoubleOrFloat(Integer.MAX_VALUE + 1L + 0.9e-6));
+        cipEqu(Integer.MIN_VALUE - 1L - 0.9e-6,
+                new OverloadedNumberUtil.DoubleOrFloat(Integer.MIN_VALUE - 1L - 0.9e-6));
+        cipEqu(Long.MAX_VALUE + 0.9e-6,
+                new OverloadedNumberUtil.DoubleOrFloat(Long.MAX_VALUE + 0.9e-6));
+        cipEqu(Long.MIN_VALUE - 0.9e-6,
+                new OverloadedNumberUtil.DoubleOrFloat(Long.MIN_VALUE - 0.9e-6));
+    }
+
+    @SuppressWarnings("boxing")
+    public void testFloatCoercion() {
+        cipEqu(1.00002f);
+        cipEqu(-1.00002f);
+        cipEqu(1.999989f);
+        cipEqu(-1.999989f);
+        cipEqu(16777218f);
+        cipEqu(-16777218f);
+        
+        cipEqu(1f, new OverloadedNumberUtil.FloatOrByte(1f, (byte) 1));
+        cipEqu(-1f, new OverloadedNumberUtil.FloatOrByte(-1f, (byte) -1));
+        cipEqu(1.000009f, new OverloadedNumberUtil.FloatOrByte(1.000009f, (byte) 1));
+        cipEqu(-1.000009f, new OverloadedNumberUtil.FloatOrByte(-1.000009f, (byte) -1));
+        cipEqu(1.999991f, new OverloadedNumberUtil.FloatOrByte(1.999991f, (byte) 2));
+        cipEqu(-1.999991f, new OverloadedNumberUtil.FloatOrByte(-1.999991f, (byte) -2));
+        
+        cipEqu(1000f, new OverloadedNumberUtil.FloatOrShort(1000f, (short) 1000));
+        cipEqu(-1000f, new OverloadedNumberUtil.FloatOrShort(-1000f, (short) -1000));
+        cipEqu(1000.00006f);
+
+        cipEqu(60000f, new OverloadedNumberUtil.FloatOrInteger(60000f, 60000));
+        cipEqu(-60000f, new OverloadedNumberUtil.FloatOrInteger(-60000f, -60000));
+        cipEqu(60000.004f);
+
+        cipEqu(100f, new OverloadedNumberUtil.FloatOrByte(100f, (byte) 100), TypeFlags.MASK_KNOWN_INTEGERS);
+        cipEqu(1000f, new OverloadedNumberUtil.FloatOrShort(1000f, (short) 1000), TypeFlags.MASK_KNOWN_INTEGERS);
+        cipEqu(60000f, new OverloadedNumberUtil.FloatOrInteger(60000f, 60000), TypeFlags.MASK_KNOWN_INTEGERS);
+        cipEqu(60000f, new OverloadedNumberUtil.FloatOrInteger(60000f, 60000), TypeFlags.LONG);
+        cipEqu((float) Integer.MAX_VALUE, (float) Integer.MAX_VALUE, TypeFlags.LONG);
+        cipEqu((float) -Integer.MAX_VALUE, (float) -Integer.MAX_VALUE);
+        
+        cipEqu(0.5f, 0.5f, TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(0.5f, 0.5f, TypeFlags.DOUBLE);
+    }
+    
+    public void testBigIntegerCoercion() {
+        BigInteger bi;
+        
+        cipEqu(BigInteger.ZERO, new OverloadedNumberUtil.BigIntegerOrByte(BigInteger.ZERO));
+        bi = new BigInteger(String.valueOf(Byte.MAX_VALUE));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrByte(bi));
+        bi = new BigInteger(String.valueOf(Byte.MIN_VALUE));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrByte(bi));
+        
+        bi = new BigInteger(String.valueOf(Byte.MAX_VALUE + 1));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrShort(bi));
+        bi = new BigInteger(String.valueOf(Byte.MIN_VALUE - 1));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrShort(bi));
+        bi = new BigInteger(String.valueOf(Short.MAX_VALUE));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrShort(bi));
+        bi = new BigInteger(String.valueOf(Short.MIN_VALUE));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrShort(bi));
+        
+        bi = new BigInteger(String.valueOf(Short.MAX_VALUE + 1));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrInteger(bi));
+        bi = new BigInteger(String.valueOf(Short.MIN_VALUE - 1));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrInteger(bi));
+        bi = new BigInteger(String.valueOf(Integer.MAX_VALUE));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrInteger(bi));
+        bi = new BigInteger(String.valueOf(Integer.MIN_VALUE));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrInteger(bi));
+        
+        bi = new BigInteger(String.valueOf(Integer.MAX_VALUE + 1L));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrLong(bi));
+        bi = new BigInteger(String.valueOf(Integer.MIN_VALUE - 1L));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrLong(bi));
+        bi = new BigInteger(String.valueOf(Long.MAX_VALUE));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrLong(bi));
+        bi = new BigInteger(String.valueOf(Long.MIN_VALUE));
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrLong(bi));
+        
+        cipEqu(new BigInteger(String.valueOf(Long.MAX_VALUE)).add(BigInteger.ONE));
+        cipEqu(new BigInteger(String.valueOf(Long.MIN_VALUE)).subtract(BigInteger.ONE));
+        
+        bi = new BigInteger("0");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi),
+                TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi),
+                TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi),
+                TypeFlags.FLOAT);
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi),
+                TypeFlags.DOUBLE);
+
+        bi = new BigInteger("16777215");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        bi = new BigInteger("-16777215");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        
+        bi = new BigInteger("16777216");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        bi = new BigInteger("-16777216");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        
+        bi = new BigInteger("16777217");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, TypeFlags.FLOAT);
+        bi = new BigInteger("-16777217");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, TypeFlags.FLOAT);
+        
+        bi = new BigInteger("9007199254740991");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.DOUBLE);
+        bi = new BigInteger("-9007199254740991");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.DOUBLE);
+        
+        bi = new BigInteger("9007199254740992");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.DOUBLE);
+        bi = new BigInteger("-9007199254740992");
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.DOUBLE);
+        
+        bi = new BigInteger("9007199254740993");
+        cipEqu(bi, TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(bi, TypeFlags.FLOAT);
+        cipEqu(bi, TypeFlags.DOUBLE);
+        bi = new BigInteger("-9007199254740993");
+        cipEqu(bi, TypeFlags.MASK_KNOWN_NONINTEGERS);
+        cipEqu(bi, TypeFlags.DOUBLE | TypeFlags.FLOAT);
+        cipEqu(bi, TypeFlags.FLOAT);
+        cipEqu(bi, TypeFlags.DOUBLE);
+        
+        bi = new BigInteger("9007199254740994");
+        cipEqu(bi, TypeFlags.MASK_KNOWN_NONINTEGERS);
+        bi = new BigInteger("-9007199254740994");
+        cipEqu(bi, TypeFlags.MASK_KNOWN_NONINTEGERS);
+    }
+    
+    private void cipEqu(Number actualAndExpected) {
+        cipEqu(actualAndExpected, actualAndExpected, -1);
+    }
+    
+    private void cipEqu(Number actual, Number expected) {
+        cipEqu(actual, expected, -1);
+    }
+
+    private void cipEqu(Number actualAndExpected, int flags) {
+        cipEqu(actualAndExpected, actualAndExpected, flags);
+    }
+    
+    @SuppressWarnings("boxing")
+    private void cipEqu(Number actual, Number expected, int flags) {
+        Number res = OverloadedNumberUtil.addFallbackType(actual, flags);
+        assertEquals(expected.getClass(), res.getClass());
+        assertEquals(expected, res);
+        
+        // Some number types wrap the number with multiple types and equals() only compares one of them. So we try to
+        // catch any inconsistency:
+        assertEquals(expected.byteValue(), res.byteValue());
+        assertEquals(expected.shortValue(), res.shortValue());
+        assertEquals(expected.intValue(), res.intValue());
+        assertEquals(expected.longValue(), res.longValue());
+        assertEquals(expected.floatValue(), res.floatValue());
+        assertEquals(expected.doubleValue(), res.doubleValue());
+    }
+    
+    public void testGetArgumentConversionPrice() {
+        // Unknown number types:
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                RationalNumber.class, Integer.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, RationalNumber.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                RationalNumber.class, Float.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                Float.class, RationalNumber.class));
+        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
+                RationalNumber.class, RationalNumber.class));
+        
+        // Fully check some rows (not all of them; the code is generated anyways):
+
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, Byte.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, Short.class));
+        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, Integer.class));
+        assertEquals(10004, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, Long.class));
+        assertEquals(10005, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, BigInteger.class));
+        assertEquals(30006, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, Float.class));
+        assertEquals(20007, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, Double.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, BigDecimal.class));
+        
+        assertEquals(45001, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, Byte.class));
+        assertEquals(44002, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, Short.class));
+        assertEquals(41003, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, Integer.class));
+        assertEquals(41004, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, Long.class));
+        assertEquals(40005, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, BigInteger.class));
+        assertEquals(33006, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, Float.class));
+        assertEquals(32007, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, Double.class));
+        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, BigDecimal.class));
+        
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, Byte.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, Short.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, Integer.class));
+        assertEquals(21004, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, Long.class));
+        assertEquals(21005, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, BigInteger.class));
+        assertEquals(40006, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, Float.class));
+        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, Double.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, BigDecimal.class));
+        
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, Byte.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, Short.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, Integer.class));
+        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, Long.class));
+        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, BigInteger.class));
+        assertEquals(40006, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, Float.class));
+        assertEquals(20007, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, Double.class));
+        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, BigDecimal.class));
+        
+        // Check if all fromC is present:
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                Byte.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                Short.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                Integer.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                Long.class, BigDecimal.class));
+        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigInteger.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                Float.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                Double.class, BigDecimal.class));
+        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
+                BigDecimal.class, BigDecimal.class));
+        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.IntegerBigDecimal.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrFloat.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.FloatOrByte.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrShort.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.FloatOrByte.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.FloatOrShort.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.FloatOrInteger.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrByte.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrIntegerOrFloat.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrInteger.class, BigDecimal.class));
+        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.DoubleOrLong.class, BigDecimal.class));
+        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrByte.class, BigDecimal.class));
+        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrShort.class, BigDecimal.class));
+        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrInteger.class, BigDecimal.class));
+        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrLong.class, BigDecimal.class));
+        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrFloat.class, BigDecimal.class));
+        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
+                OverloadedNumberUtil.BigIntegerOrDouble.class, BigDecimal.class));
+    }
+    
+}


[20/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.


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

Branch: refs/heads/3
Commit: 051a0822329aae44642b491fb1a10e75ad4ea778
Parents: 3187a09
Author: ddekany <dd...@apache.org>
Authored: Mon Feb 27 10:36:49 2017 +0100
Committer: ddekany <dd...@apache.org>
Committed: Tue Feb 28 23:57:22 2017 +0100

----------------------------------------------------------------------
 .../Java-code-style-FreeMarker.xml              |   20 +-
 .../apache/freemarker/core/ASTExpression.java   |    2 +-
 .../core/BuiltInsForMultipleTypes.java          |    4 +-
 .../freemarker/core/BuiltInsForSequences.java   |    2 +-
 .../freemarker/core/BuiltInsForStringsMisc.java |   14 +-
 .../apache/freemarker/core/Configurable.java    |   14 +-
 .../org/apache/freemarker/core/_CoreLogs.java   |   12 +-
 .../core/_ErrorDescriptionBuilder.java          |    2 +-
 .../core/_ObjectBuilderSettingEvaluator.java    |    6 +-
 .../core/_SettingEvaluationEnvironment.java     |    8 +-
 .../freemarker/core/model/ObjectWrapper.java    |    8 +-
 .../core/model/TemplateMethodModel.java         |    2 +-
 .../core/model/TemplateMethodModelEx.java       |    2 +-
 .../freemarker/core/model/impl/APIModel.java    |   45 +
 .../core/model/impl/ArgumentTypes.java          |  647 +++++++
 .../freemarker/core/model/impl/ArrayModel.java  |  126 ++
 .../freemarker/core/model/impl/BeanModel.java   |  362 ++++
 .../core/model/impl/BeansModelCache.java        |   73 +
 .../core/model/impl/BooleanModel.java           |   40 +
 .../model/impl/CallableMemberDescriptor.java    |   56 +
 .../core/model/impl/CharacterOrString.java      |   45 +
 .../core/model/impl/ClassBasedModelFactory.java |  148 ++
 .../core/model/impl/ClassChangeNotifier.java    |   32 +
 .../core/model/impl/ClassIntrospector.java      |  807 +++++++++
 .../model/impl/ClassIntrospectorBuilder.java    |  190 ++
 .../core/model/impl/CollectionAdapter.java      |   88 +
 .../core/model/impl/CollectionModel.java        |  109 ++
 .../freemarker/core/model/impl/DateModel.java   |   76 +
 .../model/impl/DefaultEnumerationAdapter.java   |  121 ++
 .../core/model/impl/DefaultObjectWrapper.java   | 1474 +++++++++++++++-
 .../model/impl/DefaultObjectWrapperBuilder.java |  100 +-
 .../impl/DefaultObjectWrapperConfiguration.java |  187 +-
 .../impl/EmptyCallableMemberDescriptor.java     |   35 +
 .../model/impl/EmptyMemberAndArguments.java     |   93 +
 .../core/model/impl/EnumerationModel.java       |  108 ++
 .../freemarker/core/model/impl/HashAdapter.java |  180 ++
 .../model/impl/InvalidPropertyException.java    |   34 +
 .../core/model/impl/IteratorModel.java          |  112 ++
 .../model/impl/JRebelClassChangeNotifier.java   |   58 +
 .../freemarker/core/model/impl/MapModel.java    |  120 ++
 .../MaybeEmptyCallableMemberDescriptor.java     |   25 +
 .../impl/MaybeEmptyMemberAndArguments.java      |   22 +
 .../core/model/impl/MemberAndArguments.java     |   64 +
 .../model/impl/MethodAppearanceFineTuner.java   |  156 ++
 .../core/model/impl/MethodSorter.java           |   32 +
 .../freemarker/core/model/impl/ModelCache.java  |  143 ++
 .../core/model/impl/ModelFactory.java           |   34 +
 .../NonPrimitiveArrayBackedReadOnlyList.java    |   42 +
 .../freemarker/core/model/impl/NumberModel.java |   60 +
 .../model/impl/OverloadedFixArgsMethods.java    |   99 ++
 .../core/model/impl/OverloadedMethods.java      |  271 +++
 .../core/model/impl/OverloadedMethodsModel.java |   82 +
 .../model/impl/OverloadedMethodsSubset.java     |  402 +++++
 .../core/model/impl/OverloadedNumberUtil.java   | 1289 ++++++++++++++
 .../model/impl/OverloadedVarArgsMethods.java    |  245 +++
 .../impl/PrimtiveArrayBackedReadOnlyList.java   |   47 +
 .../ReflectionCallableMemberDescriptor.java     |   95 +
 .../core/model/impl/ResourceBundleModel.java    |  190 ++
 .../core/model/impl/SequenceAdapter.java        |   68 +
 .../freemarker/core/model/impl/SetAdapter.java  |   32 +
 .../freemarker/core/model/impl/SimpleHash.java  |    5 +-
 .../core/model/impl/SimpleMapModel.java         |  129 ++
 .../core/model/impl/SimpleMethod.java           |  174 ++
 .../core/model/impl/SimpleMethodModel.java      |  133 ++
 .../core/model/impl/SimpleObjectWrapper.java    |   19 +-
 .../core/model/impl/SimpleSequence.java         |    5 +-
 .../core/model/impl/SingletonCustomizer.java    |   51 +
 .../freemarker/core/model/impl/StaticModel.java |  177 ++
 .../core/model/impl/StaticModels.java           |   43 +
 .../freemarker/core/model/impl/StringModel.java |   63 +
 .../freemarker/core/model/impl/TypeFlags.java   |  130 ++
 .../core/model/impl/UnsafeMethods.java          |  112 ++
 .../freemarker/core/model/impl/_EnumModels.java |   54 +
 .../freemarker/core/model/impl/_MethodUtil.java |  293 ++++
 .../freemarker/core/model/impl/_ModelAPI.java   |  220 +++
 .../core/model/impl/_ModelImplApi.java          |   37 -
 .../core/model/impl/_StaticObjectWrappers.java  |    5 -
 .../core/model/impl/beans/APIModel.java         |   45 -
 .../core/model/impl/beans/ArgumentTypes.java    |  647 -------
 .../core/model/impl/beans/ArrayModel.java       |  126 --
 .../core/model/impl/beans/BeanModel.java        |  365 ----
 .../core/model/impl/beans/BeansModelCache.java  |   73 -
 .../core/model/impl/beans/BeansWrapper.java     | 1644 ------------------
 .../model/impl/beans/BeansWrapperBuilder.java   |  159 --
 .../impl/beans/BeansWrapperConfiguration.java   |  232 ---
 .../core/model/impl/beans/BooleanModel.java     |   40 -
 .../impl/beans/CallableMemberDescriptor.java    |   56 -
 .../model/impl/beans/CharacterOrString.java     |   45 -
 .../impl/beans/ClassBasedModelFactory.java      |  148 --
 .../model/impl/beans/ClassChangeNotifier.java   |   32 -
 .../model/impl/beans/ClassIntrospector.java     |  808 ---------
 .../impl/beans/ClassIntrospectorBuilder.java    |  190 --
 .../model/impl/beans/CollectionAdapter.java     |   88 -
 .../core/model/impl/beans/CollectionModel.java  |  109 --
 .../core/model/impl/beans/DateModel.java        |   79 -
 .../beans/EmptyCallableMemberDescriptor.java    |   35 -
 .../impl/beans/EmptyMemberAndArguments.java     |   93 -
 .../core/model/impl/beans/EnumerationModel.java |  108 --
 .../core/model/impl/beans/HashAdapter.java      |  180 --
 .../impl/beans/InvalidPropertyException.java    |   34 -
 .../core/model/impl/beans/IteratorModel.java    |  112 --
 .../impl/beans/JRebelClassChangeNotifier.java   |   58 -
 .../core/model/impl/beans/MapModel.java         |  120 --
 .../MaybeEmptyCallableMemberDescriptor.java     |   25 -
 .../beans/MaybeEmptyMemberAndArguments.java     |   22 -
 .../model/impl/beans/MemberAndArguments.java    |   64 -
 .../impl/beans/MethodAppearanceFineTuner.java   |   94 -
 .../core/model/impl/beans/MethodSorter.java     |   32 -
 .../core/model/impl/beans/ModelCache.java       |  143 --
 .../core/model/impl/beans/ModelFactory.java     |   34 -
 .../NonPrimitiveArrayBackedReadOnlyList.java    |   42 -
 .../core/model/impl/beans/NumberModel.java      |   60 -
 .../impl/beans/OverloadedFixArgsMethods.java    |   99 --
 .../model/impl/beans/OverloadedMethods.java     |  271 ---
 .../impl/beans/OverloadedMethodsModel.java      |   83 -
 .../impl/beans/OverloadedMethodsSubset.java     |  402 -----
 .../model/impl/beans/OverloadedNumberUtil.java  | 1289 --------------
 .../impl/beans/OverloadedVarArgsMethods.java    |  245 ---
 .../beans/PrimtiveArrayBackedReadOnlyList.java  |   47 -
 .../ReflectionCallableMemberDescriptor.java     |   95 -
 .../model/impl/beans/ResourceBundleModel.java   |  190 --
 .../core/model/impl/beans/SequenceAdapter.java  |   68 -
 .../core/model/impl/beans/SetAdapter.java       |   32 -
 .../core/model/impl/beans/SimpleMapModel.java   |  132 --
 .../core/model/impl/beans/SimpleMethod.java     |  174 --
 .../model/impl/beans/SimpleMethodModel.java     |  134 --
 .../model/impl/beans/SingletonCustomizer.java   |   51 -
 .../core/model/impl/beans/StaticModel.java      |  177 --
 .../core/model/impl/beans/StaticModels.java     |   43 -
 .../core/model/impl/beans/StringModel.java      |   63 -
 .../core/model/impl/beans/TypeFlags.java        |  130 --
 .../core/model/impl/beans/UnsafeMethods.java    |  112 --
 .../core/model/impl/beans/_BeansAPI.java        |  220 ---
 .../core/model/impl/beans/_EnumModels.java      |   54 -
 .../core/model/impl/beans/_MethodUtil.java      |  293 ----
 .../core/model/impl/beans/package.html          |   39 -
 .../freemarker/core/model/impl/package.html     |   39 +
 .../apache/freemarker/core/util/FTLUtil.java    |   22 +-
 .../apache/freemarker/core/util/_ClassUtil.java |    2 +-
 .../freemarker/servlet/FreemarkerServlet.java   |    4 -
 .../jsp/CustomTagAndELFunctionCombiner.java     |    2 +-
 .../freemarker/servlet/jsp/JspContextModel.java |    2 +-
 .../freemarker/servlet/jsp/JspTagModelBase.java |    6 +-
 .../freemarker/servlet/jsp/TaglibFactory.java   |   17 +-
 .../model/impl/beans/unsafeMethods.properties   |   98 --
 .../core/model/impl/unsafeMethods.properties    |   98 ++
 src/manual/en_US/FM3-CHANGE-LOG.txt             |    6 +-
 .../freemarker/core/ConfigurationTest.java      |    7 +-
 .../freemarker/core/IteratorIssuesTest.java     |   43 +-
 .../core/ObjectBuilderSettingsTest.java         |   40 +-
 .../impl/AbstractParallelIntrospectionTest.java |  125 ++
 .../model/impl/AlphabeticalMethodSorter.java    |   49 +
 .../CommonSupertypeForUnwrappingHintTest.java   |  129 ++
 .../model/impl/DefaultObjectWrapperDesc.java    |   30 +
 .../model/impl/DefaultObjectWrapperInc.java     |   30 +
 ...jectWrapperModelFactoryRegistrationTest.java |   94 +
 .../impl/DefaultObjectWrapperReadOnlyTest.java  |   87 +
 .../DefaultObjectWrapperSingletonsTest.java     |  689 ++++++++
 .../model/impl/DefaultObjectWrapperTest.java    |  191 +-
 .../DefaultObjectWrapperWithShortedMethods.java |   41 +
 .../DefaultObjectWrapperWithSortedMethods.java  |   40 +
 .../core/model/impl/EnumModelsTest.java         |   84 +
 .../core/model/impl/ErrorMessagesTest.java      |  170 ++
 .../impl/FineTuneMethodAppearanceTest.java      |   64 +
 .../GetlessMethodsAsPropertyGettersRule.java    |   67 +
 .../core/model/impl/IsApplicableTest.java       |  171 ++
 .../impl/IsMoreSpecificParameterTypeTest.java   |   98 ++
 .../Java7MembersOnlyDefaultObjectWrapper.java   |  102 ++
 .../impl/ManyObjectsOfDifferentClasses.java     |  249 +++
 .../impl/ManyStaticsOfDifferentClasses.java     |  236 +++
 .../model/impl/MiscNumericalOperationsTest.java |  111 ++
 .../model/impl/ModelAPINewInstanceTest.java     |  134 ++
 .../core/model/impl/ModelCacheTest.java         |   69 +
 .../model/impl/OverloadedNumberUtilTest.java    |  585 +++++++
 .../impl/ParameterListPreferabilityTest.java    |  445 +++++
 .../impl/PrallelObjectIntrospectionTest.java    |   43 +
 .../impl/PrallelStaticIntrospectionTest.java    |   47 +
 .../core/model/impl/RationalNumber.java         |   90 +
 .../core/model/impl/StaticModelsTest.java       |   90 +
 .../core/model/impl/TypeFlagsTest.java          |  671 +++++++
 .../AbstractParallelIntrospectionTest.java      |  125 --
 .../impl/beans/AlphabeticalMethodSorter.java    |   49 -
 .../impl/beans/BeansAPINewInstanceTest.java     |  134 --
 .../model/impl/beans/BeansWrapperBasics.java    |   62 -
 .../impl/beans/BeansWrapperCachesTest.java      |   99 --
 .../core/model/impl/beans/BeansWrapperDesc.java |   30 -
 .../core/model/impl/beans/BeansWrapperInc.java  |   30 -
 .../model/impl/beans/BeansWrapperMiscTest.java  |   57 -
 .../impl/beans/BeansWrapperReadOnlyTest.java    |   95 -
 .../impl/beans/BeansWrapperSingletonsTest.java  |  743 --------
 .../beans/BeansWrapperWithShortedMethods.java   |   41 -
 .../CommonSupertypeForUnwrappingHintTest.java   |  129 --
 .../impl/beans/DefaultObjectWrapperDesc.java    |   30 -
 .../impl/beans/DefaultObjectWrapperInc.java     |   30 -
 .../DefaultObjectWrapperWithSortedMethods.java  |   41 -
 .../core/model/impl/beans/EnumModelsTest.java   |   84 -
 .../model/impl/beans/ErrorMessagesTest.java     |  170 --
 .../beans/FineTuneMethodAppearanceTest.java     |   65 -
 .../GetlessMethodsAsPropertyGettersRule.java    |   70 -
 .../core/model/impl/beans/IsApplicableTest.java |  171 --
 .../beans/IsMoreSpecificParameterTypeTest.java  |   98 --
 .../beans/Java7MembersOnlyBeansWrapper.java     |  102 --
 .../beans/ManyObjectsOfDifferentClasses.java    |  249 ---
 .../beans/ManyStaticsOfDifferentClasses.java    |  236 ---
 .../impl/beans/MiscNumericalOperationsTest.java |  111 --
 .../core/model/impl/beans/ModelCacheTest.java   |   65 -
 .../impl/beans/OverloadedNumberUtilTest.java    |  585 -------
 .../beans/ParameterListPreferabilityTest.java   |  445 -----
 .../beans/PrallelObjectIntrospectionTest.java   |   43 -
 .../beans/PrallelStaticIntrospectionTest.java   |   47 -
 .../core/model/impl/beans/RationalNumber.java   |   90 -
 .../core/model/impl/beans/StaticModelsTest.java |   90 -
 .../core/model/impl/beans/TypeFlagsTest.java    |  671 -------
 .../servlet/jsp/RealServletContainertTest.java  |   19 +-
 .../freemarker/servlet/jsp/TLDParsingTest.java  |    8 +-
 .../test/templatesuite/TemplateTestCase.java    |   63 +-
 .../test/templatesuite/models/Listables.java    |    5 -
 .../models/OverloadedMethods2.java              |    2 +-
 .../SimpleMapAndCollectionObjectWrapper.java    |    3 +-
 .../expected/attributes-modernModels.txt        |    4 +-
 .../basic/WEB-INF/expected/attributes.txt       |    4 +-
 .../test/templatesuite/expected/bean-maps.txt   |  115 --
 .../test/templatesuite/expected/beans.txt       |   56 -
 .../expected/default-object-wrapper.txt         |   56 +
 .../test/templatesuite/expected/listhash.txt    |   44 -
 .../templatesuite/templates/api-builtins.ftl    |    8 +-
 .../test/templatesuite/templates/bean-maps.ftl  |   58 -
 .../test/templatesuite/templates/beans.ftl      |   60 -
 .../templates/default-object-wrapper.ftl        |   60 +
 .../templates/overloaded-methods.ftl            |    4 +-
 .../freemarker/test/templatesuite/testcases.xml |   53 +-
 231 files changed, 15741 insertions(+), 16580 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
----------------------------------------------------------------------
diff --git a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
index 9615559..ae87ca3 100644
--- a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
+++ b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
@@ -1,3 +1,21 @@
+<!--
+  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.
+-->
 <code_scheme name="FreeMarker">
   <option name="LINE_SEPARATOR" value="&#xA;" />
   <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
@@ -27,7 +45,7 @@
   <option name="JD_PARAM_DESCRIPTION_ON_NEW_LINE" value="true" />
   <option name="WRAP_COMMENTS" value="true" />
   <JavaCodeStyleSettings>
-    <option name="CLASS_NAMES_IN_JAVADOC" value="2" />
+    <option name="CLASS_NAMES_IN_JAVADOC" value="3" />
   </JavaCodeStyleSettings>
   <codeStyleSettings language="JAVA">
     <option name="RIGHT_MARGIN" value="120" />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 6635e35..af08ccd 100644
--- a/src/main/java/org/apache/freemarker/core/ASTExpression.java
+++ b/src/main/java/org/apache/freemarker/core/ASTExpression.java
@@ -29,7 +29,7 @@ import org.apache.freemarker.core.model.TemplateModelException;
 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.beans.BeanModel;
+import org.apache.freemarker.core.model.impl.BeanModel;
 
 /**
  * AST expression node superclass

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
index b7bd7f6..d7f0249 100644
--- a/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
@@ -42,8 +42,8 @@ import org.apache.freemarker.core.model.TemplateTransformModel;
 import org.apache.freemarker.core.model.impl.SimpleDate;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.apache.freemarker.core.model.impl.beans.OverloadedMethodsModel;
-import org.apache.freemarker.core.model.impl.beans.SimpleMethodModel;
+import org.apache.freemarker.core.model.impl.OverloadedMethodsModel;
+import org.apache.freemarker.core.model.impl.SimpleMethodModel;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.valueformat.TemplateDateFormat;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormat;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java b/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
index 92c59b8..c54328c 100644
--- a/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
@@ -44,7 +44,7 @@ import org.apache.freemarker.core.model.impl.CollectionAndSequence;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.model.impl.SimpleScalar;
 import org.apache.freemarker.core.model.impl.TemplateModelListSequence;
-import org.apache.freemarker.core.model.impl.beans.CollectionModel;
+import org.apache.freemarker.core.model.impl.CollectionModel;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._StringUtil;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
index 12e1207..712ff76 100644
--- a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -35,8 +35,8 @@ import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.TemplateTransformModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
-import org.apache.freemarker.core.model.impl.beans.BeanModel;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
+import org.apache.freemarker.core.model.impl.BeanModel;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 
 class BuiltInsForStringsMisc {
 
@@ -289,11 +289,11 @@ class BuiltInsForStringsMisc {
             @Override
             public Object exec(List arguments) throws TemplateModelException {
                 ObjectWrapper ow = env.getObjectWrapper();
-                BeansWrapper bw = 
-                    ow instanceof BeansWrapper 
-                    ? (BeansWrapper) ow
-                    : _StaticObjectWrappers.BEANS_WRAPPER;
-                return bw.newInstance(cl, arguments);
+                DefaultObjectWrapper dow =
+                    ow instanceof DefaultObjectWrapper
+                    ? (DefaultObjectWrapper) ow
+                    : _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER;
+                return dow.newInstance(cl, arguments);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 78cd125..c1252d5 100644
--- a/src/main/java/org/apache/freemarker/core/Configurable.java
+++ b/src/main/java/org/apache/freemarker/core/Configurable.java
@@ -48,7 +48,6 @@ import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.SimpleObjectWrapper;
 import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
 import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
@@ -1805,18 +1804,17 @@ public class Configurable {
      *   <li><p>{@code "object_wrapper"}:
      *       See {@link #setObjectWrapper(ObjectWrapper)}.
      *       <br>String value: If the value contains dot, then it's interpreted as an <a href="#fm_obe">object builder
-     *       expression</a>, with the addition that {@link BeansWrapper}, {@link DefaultObjectWrapper} and
+     *       expression</a>, with the addition that {@link DefaultObjectWrapper}, {@link DefaultObjectWrapper} and
      *       {@link SimpleObjectWrapper} can be referred without package name. For example, these strings are valid
      *       values: {@code "DefaultObjectWrapper(3.0.0)"},
-     *       {@code "BeansWrapper(2.3.21, simpleMapWrapper=true)"}.
+     *       {@code "DefaultObjectWrapper(2.3.21, simpleMapWrapper=true)"}.
      *       <br>If the value does not contain dot, then it must be one of these special values (case insensitive):
      *       {@code "default"} means the default of {@link Configuration} (the default depends on the
      *       {@code Configuration#Configuration(Version) incompatible_improvements}, but a bug existed in 2.3.21 where
      *       that was ignored),
      *       {@code "default_2_3_0"} (means {@link _StaticObjectWrappers#DEFAULT_OBJECT_WRAPPER})
-     *       {@code "simple"} (means the deprecated {@link _StaticObjectWrappers#SIMPLE_OBJECT_WRAPPER},
-     *       {@code "beans"} (means {@link _StaticObjectWrappers#BEANS_WRAPPER})
-     *       
+     *       {@code "simple"} (means the deprecated {@link _StaticObjectWrappers#SIMPLE_OBJECT_WRAPPER}
+     *
      *   <li><p>{@code "number_format"}: See {@link #setNumberFormat(String)}.
      *   
      *   <li><p>{@code "boolean_format"}: See {@link #setBooleanFormat(String)} .
@@ -2116,7 +2114,7 @@ public class Configurable {
      *   </li>
      *   <li>
      *     <p>The following classes can be referred to with simple (unqualified) name instead of fully qualified name:
-     *     {@link DefaultObjectWrapper}, {@link BeansWrapper}, {@link SimpleObjectWrapper}, {@link Locale},
+     *     {@link DefaultObjectWrapper}, {@link DefaultObjectWrapper}, {@link SimpleObjectWrapper}, {@link Locale},
      *     {@link TemplateConfiguration}, {@link PathGlobMatcher}, {@link FileNameGlobMatcher}, {@link PathRegexMatcher},
      *     {@link AndMatcher}, {@link OrMatcher}, {@link NotMatcher}, {@link ConditionalTemplateConfigurationFactory},
      *     {@link MergingTemplateConfigurationFactory}, {@link FirstMatchTemplateConfigurationFactory},
@@ -2216,8 +2214,6 @@ public class Configurable {
                     }
                 } else if ("simple".equalsIgnoreCase(value)) {
                     setObjectWrapper(_StaticObjectWrappers.SIMPLE_OBJECT_WRAPPER);
-                } else if ("beans".equalsIgnoreCase(value)) {
-                    setObjectWrapper(_StaticObjectWrappers.BEANS_WRAPPER);
                 } else {
                     setObjectWrapper((ObjectWrapper) _ObjectBuilderSettingEvaluator.eval(
                                     value, ObjectWrapper.class, false, _SettingEvaluationEnvironment.getCurrent()));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/_CoreLogs.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_CoreLogs.java b/src/main/java/org/apache/freemarker/core/_CoreLogs.java
index 1ccb915..3523255 100644
--- a/src/main/java/org/apache/freemarker/core/_CoreLogs.java
+++ b/src/main/java/org/apache/freemarker/core/_CoreLogs.java
@@ -27,11 +27,13 @@ import org.slf4j.LoggerFactory;
  * access things inside this package that users shouldn't. 
  */ 
 public final class _CoreLogs {
-    
-    public static final Logger RUNTIME = LoggerFactory.getLogger("org.apache.freemarker.core.runtime");
-    public static final Logger ATTEMPT = LoggerFactory.getLogger("org.apache.freemarker.core.runtime.attempt");
-    public static final Logger SECURITY = LoggerFactory.getLogger("org.apache.freemarker.core.security");
-    public static final Logger BEANS_WRAPPER = LoggerFactory.getLogger("org.apache.freemarker.core.model.impl.beans");
+
+    // [FM3] Why "Runtime"? "TemplateProcessing" maybe?
+    public static final Logger RUNTIME = LoggerFactory.getLogger("org.apache.freemarker.core.Runtime");
+    public static final Logger ATTEMPT = LoggerFactory.getLogger("org.apache.freemarker.core.Runtime.Attempt");
+    public static final Logger SECURITY = LoggerFactory.getLogger("org.apache.freemarker.core.Security");
+    public static final Logger OBJECT_WRAPPER = LoggerFactory.getLogger("org.apache.freemarker.core.model" +
+            ".ObjectWrapper");
     public static final Logger TEMPLATE_RESOLVER = LoggerFactory.getLogger(
             "org.apache.freemarker.core.templateresolver");
     public static final Logger DEBUG_SERVER = LoggerFactory.getLogger("org.apache.freemarker.core.debug.server");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java b/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
index 812aa5a..93ae5ac 100644
--- a/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
+++ b/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
@@ -23,7 +23,7 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 
-import org.apache.freemarker.core.model.impl.beans._MethodUtil;
+import org.apache.freemarker.core.model.impl._MethodUtil;
 import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.core.util._StringUtil;
 import org.slf4j.Logger;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 9c18db0..97f0ce5 100644
--- a/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -41,7 +41,7 @@ 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.SimpleObjectWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
 import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
 import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
@@ -658,7 +658,7 @@ public class _ObjectBuilderSettingEvaluator {
             SHORTHANDS = new HashMap/*<String,String>*/();
             
             addWithSimpleName(SHORTHANDS, DefaultObjectWrapper.class);
-            addWithSimpleName(SHORTHANDS, BeansWrapper.class);
+            addWithSimpleName(SHORTHANDS, DefaultObjectWrapper.class);
             addWithSimpleName(SHORTHANDS, SimpleObjectWrapper.class);
 
             addWithSimpleName(SHORTHANDS, TemplateConfiguration.class);
@@ -981,7 +981,7 @@ public class _ObjectBuilderSettingEvaluator {
                             "Failed to call " + cl.getName() + " 0-argument constructor", e);
                 }
             } else {
-                BeansWrapper ow = env.getObjectWrapper();
+                DefaultObjectWrapper ow = env.getObjectWrapper();
                 List/*<TemplateModel>*/ tmArgs = new ArrayList(positionalParamValues.size());
                 for (int i = 0; i < positionalParamValues.size(); i++) {
                     try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 7e9fb37..f095467 100644
--- a/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
+++ b/src/main/java/org/apache/freemarker/core/_SettingEvaluationEnvironment.java
@@ -21,7 +21,7 @@ package org.apache.freemarker.core;
 
 import java.util.Properties;
 
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 
 /**
  * Don't use this; used internally by FreeMarker, might changes without notice.
@@ -31,7 +31,7 @@ public class _SettingEvaluationEnvironment {
     
     private static final ThreadLocal CURRENT = new ThreadLocal();
 
-    private BeansWrapper objectWrapper;
+    private DefaultObjectWrapper objectWrapper;
     
     public static _SettingEvaluationEnvironment getCurrent() {
         Object r = CURRENT.get();
@@ -51,9 +51,9 @@ public class _SettingEvaluationEnvironment {
         CURRENT.set(previous);
     }
 
-    public BeansWrapper getObjectWrapper() {
+    public DefaultObjectWrapper getObjectWrapper() {
         if (objectWrapper == null) {
-            objectWrapper = new BeansWrapper(Configuration.VERSION_3_0_0);
+            objectWrapper = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
         }
         return objectWrapper;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
index a321742..19b40cb 100644
--- a/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
+++ b/src/main/java/org/apache/freemarker/core/model/ObjectWrapper.java
@@ -22,15 +22,15 @@ package org.apache.freemarker.core.model;
 import java.util.Map;
 
 import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 
 /**
  * Maps Java objects to the type-system of FreeMarker Template Language (see the {@link TemplateModel}
  * interfaces). Thus this is what decides what parts of the Java objects will be accessible in the templates and how.
  * 
- * <p>For example, with a {@link BeansWrapper} both the items of {@link Map} and the JavaBean properties (the getters)
+ * <p>For example, with a {@link DefaultObjectWrapper} both the items of {@link Map} and the JavaBean properties (the getters)
  * of an object are accessible in template uniformly with the {@code myObject.foo} syntax, where "foo" is the map key or
- * the property name. This is because both kind of object is wrapped by {@link BeansWrapper} into a
+ * the property name. This is because both kind of object is wrapped by {@link DefaultObjectWrapper} into a
  * {@link TemplateHashModel} implementation that will call {@link Map#get(Object)} or the getter method, transparently
  * to the template language.
  * 
@@ -44,7 +44,7 @@ public interface ObjectWrapper {
      * 
      * @param obj The object to wrap into a {@link TemplateModel}. If it already implements {@link TemplateModel},
      *      it should just return the object as is. If it's {@code null}, the method should return {@code null}
-     *      (however, {@link BeansWrapper}, has a legacy option for returning a null model object instead, but it's not
+     *      (however, {@link DefaultObjectWrapper}, has a legacy option for returning a null model object instead, but it's not
      *      a good idea).
      * 
      * @return a {@link TemplateModel} wrapper of the object passed in. To support un-wrapping, you may consider the

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/TemplateMethodModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/TemplateMethodModel.java b/src/main/java/org/apache/freemarker/core/model/TemplateMethodModel.java
index d624ede..85adfcb 100644
--- a/src/main/java/org/apache/freemarker/core/model/TemplateMethodModel.java
+++ b/src/main/java/org/apache/freemarker/core/model/TemplateMethodModel.java
@@ -29,7 +29,7 @@ import org.apache.freemarker.core.Environment;
 
 /**
  * "method" template language data type: Objects that act like functions. The name comes from that their original
- * application was calling Java methods via {@link org.apache.freemarker.core.model.impl.beans.BeansWrapper}. 
+ * application was calling Java methods via {@link org.apache.freemarker.core.model.impl.DefaultObjectWrapper}.
  * 
  * <p>In templates they are used like {@code myMethod("foo", "bar")} or {@code myJavaObject.myJavaMethod("foo", "bar")}. 
  * 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 fdf7ec0..d06da21 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.beans.BeansWrapper}, 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 create
  * 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/051a0822/src/main/java/org/apache/freemarker/core/model/impl/APIModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/APIModel.java b/src/main/java/org/apache/freemarker/core/model/impl/APIModel.java
new file mode 100644
index 0000000..f5f686d
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/APIModel.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * Exposes the Java API (and properties) of an object.
+ * 
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The exposion level is inherited from the {@link DefaultObjectWrapper}</li>
+ * <li>But methods will always shadow properties and fields with identical name, regardless of {@link DefaultObjectWrapper}
+ * settings</li>
+ * </ul>
+ * 
+ * @since 2.3.22
+ */
+final class APIModel extends BeanModel {
+
+    APIModel(Object object, DefaultObjectWrapper wrapper) {
+        super(object, wrapper, false);
+    }
+
+    protected boolean isMethodsShadowItems() {
+        return true;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java b/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
new file mode 100644
index 0000000..6e16ecd
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
@@ -0,0 +1,647 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._ClassUtil;
+
+/**
+ * The argument types of a method call; usable as cache key.
+ */
+final class ArgumentTypes {
+    
+    /**
+     * Conversion difficulty: Lowest; Java Reflection will do it automatically.
+     */
+    private static final int CONVERSION_DIFFICULTY_REFLECTION = 0;
+
+    /**
+     * Conversion difficulty: Medium: Java reflection API won't convert it, FreeMarker has to do it.
+     */
+    private static final int CONVERSION_DIFFICULTY_FREEMARKER = 1;
+    
+    /**
+     * Conversion difficulty: Highest; conversion is not possible.
+     */
+    private static final int CONVERSION_DIFFICULTY_IMPOSSIBLE = 2;
+
+    /**
+     * The types of the arguments; for varags this contains the exploded list (not the array). 
+     */
+    private final Class<?>[] types;
+    
+    /**
+     * @param args The actual arguments. A varargs argument should be present exploded, no as an array.
+     */
+    ArgumentTypes(Object[] args) {
+        int ln = args.length;
+        Class<?>[] typesTmp = new Class[ln];
+        for (int i = 0; i < ln; ++i) {
+            Object arg = args[i];
+            typesTmp[i] = arg == null
+                    ? Null.class
+                    : arg.getClass();
+        }
+        
+        // `typesTmp` is used so the array is only modified before it's stored in the final `types` field (see JSR-133)
+        types = typesTmp;  
+    }
+    
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        for (Class<?> type : types) {
+            hash ^= type.hashCode();
+        }
+        return hash;
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof ArgumentTypes) {
+            ArgumentTypes cs = (ArgumentTypes) o;
+            if (cs.types.length != types.length) {
+                return false;
+            }
+            for (int i = 0; i < types.length; ++i) {
+                if (cs.types[i] != types[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * @return Possibly {@link EmptyCallableMemberDescriptor#NO_SUCH_METHOD} or
+     *         {@link EmptyCallableMemberDescriptor#AMBIGUOUS_METHOD}. 
+     */
+    MaybeEmptyCallableMemberDescriptor getMostSpecific(
+            List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) {
+        LinkedList<CallableMemberDescriptor> applicables = getApplicables(memberDescs, varArg);
+        if (applicables.isEmpty()) {
+            return EmptyCallableMemberDescriptor.NO_SUCH_METHOD;
+        }
+        if (applicables.size() == 1) {
+            return applicables.getFirst();
+        }
+        
+        LinkedList<CallableMemberDescriptor> maximals = new LinkedList<>();
+        for (CallableMemberDescriptor applicable : applicables) {
+            boolean lessSpecific = false;
+            for (Iterator<CallableMemberDescriptor> maximalsIter = maximals.iterator(); 
+                maximalsIter.hasNext(); ) {
+                CallableMemberDescriptor maximal = maximalsIter.next();
+                final int cmpRes = compareParameterListPreferability(
+                        applicable.getParamTypes(), maximal.getParamTypes(), varArg); 
+                if (cmpRes > 0) {
+                    maximalsIter.remove();
+                } else if (cmpRes < 0) {
+                    lessSpecific = true;
+                }
+            }
+            if (!lessSpecific) {
+                maximals.addLast(applicable);
+            }
+        }
+        if (maximals.size() > 1) {
+            return EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD;
+        }
+        return maximals.getFirst();
+    }
+
+    /**
+     * Tells if among the parameter list of two methods, which one fits this argument list better.
+     * This method assumes that the parameter lists are applicable to this argument lists; if that's not ensured,
+     * what the result will be is undefined.
+     * 
+     * <p>The decision is made by comparing the preferability of each parameter types of the same position in a loop.
+     * At the end, the parameter list with the more preferred parameters will be the preferred one. If both parameter
+     * lists has the same amount of preferred parameters, the one that has the first (lower index) preferred parameter
+     * is the preferred one. Otherwise the two parameter list are considered to be equal in terms of preferability.
+     * 
+     * <p>If there's no numerical conversion involved, the preferability of two parameter types is decided on how
+     * specific their types are. For example, {@code String} is more specific than {@link Object} (because
+     * {@code Object.class.isAssignableFrom(String.class)}-s), and so {@code String} is preferred. Primitive
+     * types are considered to be more specific than the corresponding boxing class (like {@code boolean} is more
+     * specific than {@code Boolean}, because the former can't store {@code null}). The preferability decision gets
+     * trickier when there's a possibility of numerical conversion from the actual argument type to the type of some of
+     * the parameters. If such conversion is only possible for one of the competing parameter types, that parameter
+     * automatically wins. If it's possible for both, {@link OverloadedNumberUtil#getArgumentConversionPrice} will
+     * be used to calculate the conversion "price", and the parameter type with lowest price wins. There are also
+     * a twist with array-to-list and list-to-array conversions; we try to avoid those, so the parameter where such
+     * conversion isn't needed will always win.
+     * 
+     * @param paramTypes1 The parameter types of one of the competing methods
+     * @param paramTypes2 The parameter types of the other competing method
+     * @param varArg Whether these competing methods are varargs methods. 
+     * @return More than 0 if the first parameter list is preferred, less then 0 if the other is preferred,
+     *        0 if there's no decision 
+     */
+    int compareParameterListPreferability(Class<?>[] paramTypes1, Class<?>[] paramTypes2, boolean varArg) {
+        final int argTypesLen = types.length; 
+        final int paramTypes1Len = paramTypes1.length;
+        final int paramTypes2Len = paramTypes2.length;
+        //assert varArg || paramTypes1Len == paramTypes2Len;
+        
+        int paramList1WeakWinCnt = 0;
+        int paramList2WeakWinCnt = 0;
+        int paramList1WinCnt = 0;
+        int paramList2WinCnt = 0;
+        int paramList1StrongWinCnt = 0;
+        int paramList2StrongWinCnt = 0;
+        int paramList1VeryStrongWinCnt = 0;
+        int paramList2VeryStrongWinCnt = 0;
+        int firstWinerParamList = 0;
+        for (int i = 0; i < argTypesLen; i++) {
+            final Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, i, varArg);
+            final Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, i, varArg);
+            
+            final int winerParam;  // 1 => paramType1; -1 => paramType2; 0 => draw
+            if (paramType1 == paramType2) {
+                winerParam = 0;
+            } else {
+                final Class<?> argType = types[i];
+                final boolean argIsNum = Number.class.isAssignableFrom(argType);
+                
+                final int numConvPrice1;
+                if (argIsNum && _ClassUtil.isNumerical(paramType1)) {
+                    final Class<?> nonPrimParamType1 = paramType1.isPrimitive()
+                            ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1;
+                    numConvPrice1 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType1);
+                } else {
+                    numConvPrice1 = Integer.MAX_VALUE;
+                }
+                // numConvPrice1 is Integer.MAX_VALUE if either:
+                // - argType and paramType1 aren't both numerical
+                // - FM doesn't know some of the numerical types, or the conversion between them is not allowed    
+                
+                final int numConvPrice2;
+                if (argIsNum && _ClassUtil.isNumerical(paramType2)) {
+                    final Class<?> nonPrimParamType2 = paramType2.isPrimitive()
+                            ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2;
+                    numConvPrice2 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType2);
+                } else {
+                    numConvPrice2 = Integer.MAX_VALUE;
+                }
+                
+                if (numConvPrice1 == Integer.MAX_VALUE) {
+                    if (numConvPrice2 == Integer.MAX_VALUE) {  // No numerical conversions anywhere
+                        // List to array conversions (unwrapping sometimes makes a List instead of an array)
+                        if (List.class.isAssignableFrom(argType)
+                                && (paramType1.isArray() || paramType2.isArray())) {
+                            if (paramType1.isArray()) {
+                                if (paramType2.isArray()) {  // both paramType1 and paramType2 are arrays
+                                    int r = compareParameterListPreferability_cmpTypeSpecificty(
+                                            paramType1.getComponentType(), paramType2.getComponentType());
+                                    // Because we don't know if the List items are instances of the component
+                                    // type or not, we prefer the safer choice, which is the more generic array:
+                                    if (r > 0) {
+                                        winerParam = 2;
+                                        paramList2StrongWinCnt++;
+                                    } else if (r < 0) {
+                                        winerParam = 1;
+                                        paramList1StrongWinCnt++;
+                                    } else {
+                                        winerParam = 0;
+                                    }
+                                } else {  // paramType1 is array, paramType2 isn't
+                                    // Avoid List to array conversion if the other way makes any sense:
+                                    if (Collection.class.isAssignableFrom(paramType2)) {
+                                        winerParam = 2;
+                                        paramList2StrongWinCnt++;
+                                    } else {
+                                        winerParam = 1;
+                                        paramList1WeakWinCnt++;
+                                    }
+                                }
+                            } else {  // paramType2 is array, paramType1 isn't
+                                // Avoid List to array conversion if the other way makes any sense:
+                                if (Collection.class.isAssignableFrom(paramType1)) {
+                                    winerParam = 1;
+                                    paramList1StrongWinCnt++;
+                                } else {
+                                    winerParam = 2;
+                                    paramList2WeakWinCnt++;
+                                }
+                            }
+                        } else if (argType.isArray()
+                                && (List.class.isAssignableFrom(paramType1)
+                                        || List.class.isAssignableFrom(paramType2))) {
+                            // Array to List conversions (unwrapping sometimes makes an array instead of a List)
+                            if (List.class.isAssignableFrom(paramType1)) {
+                                if (List.class.isAssignableFrom(paramType2)) {
+                                    // Both paramType1 and paramType2 extends List
+                                    winerParam = 0;
+                                } else {
+                                    // Only paramType1 extends List
+                                    winerParam = 2;
+                                    paramList2VeryStrongWinCnt++;
+                                }
+                            } else {
+                                // Only paramType2 extends List
+                                winerParam = 1;
+                                paramList1VeryStrongWinCnt++;
+                            }
+                        } else {  // No list to/from array conversion
+                            final int r = compareParameterListPreferability_cmpTypeSpecificty(
+                                    paramType1, paramType2);
+                            if (r > 0) {
+                                winerParam = 1;
+                                if (r > 1) {
+                                    paramList1WinCnt++;
+                                } else {
+                                    paramList1WeakWinCnt++;
+                                }
+                            } else if (r < 0) {
+                                winerParam = -1;
+                                if (r < -1) {
+                                    paramList2WinCnt++;
+                                } else {
+                                    paramList2WeakWinCnt++;
+                                }
+                            } else {
+                                winerParam = 0;
+                            }
+                        }
+                    } else {  // No num. conv. of param1, num. conv. of param2
+                        winerParam = -1;
+                        paramList2WinCnt++;
+                    }
+                } else if (numConvPrice2 == Integer.MAX_VALUE) {  // Num. conv. of param1, not of param2
+                    winerParam = 1;
+                    paramList1WinCnt++;
+                } else {  // Num. conv. of both param1 and param2
+                    if (numConvPrice1 != numConvPrice2) {
+                        if (numConvPrice1 < numConvPrice2) {
+                            winerParam = 1;
+                            if (numConvPrice1 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE
+                                    && numConvPrice2 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) {
+                                paramList1StrongWinCnt++;
+                            } else {
+                                paramList1WinCnt++;
+                            }
+                        } else {
+                            winerParam = -1;
+                            if (numConvPrice2 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE
+                                    && numConvPrice1 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) {
+                                paramList2StrongWinCnt++;
+                            } else {
+                                paramList2WinCnt++;
+                            }
+                        }
+                    } else {
+                        winerParam = (paramType1.isPrimitive() ? 1 : 0) - (paramType2.isPrimitive() ? 1 : 0);
+                        if (winerParam == 1) paramList1WeakWinCnt++;
+                        else if (winerParam == -1) paramList2WeakWinCnt++;
+                    }
+                }
+            }  // when paramType1 != paramType2
+            
+            if (firstWinerParamList == 0 && winerParam != 0) {
+                firstWinerParamList = winerParam; 
+            }
+        }  // for each parameter types
+        
+        if (paramList1VeryStrongWinCnt != paramList2VeryStrongWinCnt) {
+            return paramList1VeryStrongWinCnt - paramList2VeryStrongWinCnt;
+        } else if (paramList1StrongWinCnt != paramList2StrongWinCnt) {
+            return paramList1StrongWinCnt - paramList2StrongWinCnt;
+        } else if (paramList1WinCnt != paramList2WinCnt) {
+            return paramList1WinCnt - paramList2WinCnt;
+        } else if (paramList1WeakWinCnt != paramList2WeakWinCnt) {
+            return paramList1WeakWinCnt - paramList2WeakWinCnt;
+        } else if (firstWinerParamList != 0) {  // paramList1WinCnt == paramList2WinCnt
+            return firstWinerParamList;
+        } else { // still undecided
+            if (varArg) {
+                if (paramTypes1Len == paramTypes2Len) {
+                    // If we had a 0-length varargs array in both methods, we also compare the types at the
+                    // index of the varargs parameter, like if we had a single varargs argument. However, this
+                    // time we don't have an argument type, so we can only decide based on type specificity:
+                    if (argTypesLen == paramTypes1Len - 1) {
+                        Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, argTypesLen, true);
+                        Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, argTypesLen, true);
+                        if (_ClassUtil.isNumerical(paramType1) && _ClassUtil.isNumerical(paramType2)) {
+                            int r = OverloadedNumberUtil.compareNumberTypeSpecificity(paramType1, paramType2);
+                            if (r != 0) return r;
+                            // falls through
+                        }
+                        return compareParameterListPreferability_cmpTypeSpecificty(paramType1, paramType2);
+                    } else {
+                        return 0;
+                    }
+                } else {
+                    // The method with more oms parameters wins:
+                    return paramTypes1Len - paramTypes2Len;
+                }
+            } else {
+                return 0;
+            }
+        }
+    }
+    
+    /**
+     * Trivial comparison of type specificities; unaware of numerical conversions. 
+     * 
+     * @return Less-than-0, 0, or more-than-0 depending on which side is more specific. The absolute value is 1 if
+     *     the difference is only in primitive VS non-primitive, more otherwise.
+     */
+    private int compareParameterListPreferability_cmpTypeSpecificty(
+            final Class<?> paramType1, final Class<?> paramType2) {
+        // The more specific (smaller) type wins.
+        
+        final Class<?> nonPrimParamType1 = paramType1.isPrimitive()
+                ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1;
+        final Class<?> nonPrimParamType2 = paramType2.isPrimitive()
+                ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2;
+                
+        if (nonPrimParamType1 == nonPrimParamType2) {
+            if (nonPrimParamType1 != paramType1) {
+                if (nonPrimParamType2 != paramType2) {
+                    return 0;  // identical prim. types; shouldn't ever be reached
+                } else {
+                    return 1;  // param1 is prim., param2 is non prim.
+                }
+            } else if (nonPrimParamType2 != paramType2) {
+                return -1;  // param1 is non-prim., param2 is prim.
+            } else {
+                return 0;  // identical non-prim. types
+            }
+        } else if (nonPrimParamType2.isAssignableFrom(nonPrimParamType1)) {
+            return 2;
+        } else if (nonPrimParamType1.isAssignableFrom(nonPrimParamType2)) {
+            return -2;
+        } if (nonPrimParamType1 == Character.class && nonPrimParamType2.isAssignableFrom(String.class)) {
+            return 2;  // A character is a 1 long string in FTL, so we pretend that it's a String subtype.
+        } if (nonPrimParamType2 == Character.class && nonPrimParamType1.isAssignableFrom(String.class)) {
+            return -2;
+        } else {
+            return 0;  // unrelated types
+        }
+    }
+
+    private static Class<?> getParamType(Class<?>[] paramTypes, int paramTypesLen, int i, boolean varArg) {
+        return varArg && i >= paramTypesLen - 1
+                ? paramTypes[paramTypesLen - 1].getComponentType()
+                : paramTypes[i];
+    }
+    
+    /**
+     * Returns all methods that are applicable to actual
+     * parameter types represented by this ArgumentTypes object.
+     */
+    LinkedList<CallableMemberDescriptor> getApplicables(
+            List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) {
+        LinkedList<CallableMemberDescriptor> applicables = new LinkedList<>();
+        for (ReflectionCallableMemberDescriptor memberDesc : memberDescs) {
+            int difficulty = isApplicable(memberDesc, varArg);
+            if (difficulty != CONVERSION_DIFFICULTY_IMPOSSIBLE) {
+                if (difficulty == CONVERSION_DIFFICULTY_REFLECTION) {
+                    applicables.add(memberDesc);
+                } else if (difficulty == CONVERSION_DIFFICULTY_FREEMARKER) {
+                    applicables.add(new SpecialConversionCallableMemberDescriptor(memberDesc));
+                } else {
+                    throw new BugException();
+                }
+            }
+        }
+        return applicables;
+    }
+    
+    /**
+     * Returns if the supplied method is applicable to actual
+     * parameter types represented by this ArgumentTypes object, also tells
+     * how difficult that conversion is.
+     * 
+     * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants.
+     */
+    private int isApplicable(ReflectionCallableMemberDescriptor memberDesc, boolean varArg) {
+        final Class<?>[] paramTypes = memberDesc.getParamTypes(); 
+        final int cl = types.length;
+        final int fl = paramTypes.length - (varArg ? 1 : 0);
+        if (varArg) {
+            if (cl < fl) {
+                return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+            }
+        } else {
+            if (cl != fl) {
+                return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+            }
+        }
+        
+        int maxDifficulty = 0;
+        for (int i = 0; i < fl; ++i) {
+            int difficulty = isMethodInvocationConvertible(paramTypes[i], types[i]);
+            if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) {
+                return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+            }
+            if (maxDifficulty < difficulty) {
+                maxDifficulty = difficulty;
+            }
+        }
+        if (varArg) {
+            Class<?> varArgParamType = paramTypes[fl].getComponentType();
+            for (int i = fl; i < cl; ++i) {
+                int difficulty = isMethodInvocationConvertible(varArgParamType, types[i]); 
+                if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) {
+                    return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+                }
+                if (maxDifficulty < difficulty) {
+                    maxDifficulty = difficulty;
+                }
+            }
+        }
+        return maxDifficulty;
+    }
+
+    /**
+     * Determines whether a type is convertible to another type via 
+     * method invocation conversion, and if so, what kind of conversion is needed.
+     * It treates the object type counterpart of primitive types as if they were the primitive types
+     * (that is, a Boolean actual parameter type matches boolean primitive formal type). This behavior
+     * is because this method is used to determine applicable methods for 
+     * an actual parameter list, and primitive types are represented by 
+     * their object duals in reflective method calls.
+     * @param formal the parameter type to which the actual 
+     * parameter type should be convertible; possibly a primitive type
+     * @param actual the argument type; not a primitive type, maybe {@link Null}.
+     * 
+     * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants.
+     */
+    private int isMethodInvocationConvertible(final Class<?> formal, final Class<?> actual) {
+        // Check for identity or widening reference conversion
+        if (formal.isAssignableFrom(actual) && actual != CharacterOrString.class) {
+            return CONVERSION_DIFFICULTY_REFLECTION;
+        } else {
+            final Class<?> formalNP;
+            if (formal.isPrimitive()) {
+                if (actual == Null.class) {
+                    return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+                }
+                
+                formalNP = _ClassUtil.primitiveClassToBoxingClass(formal);
+                if (actual == formalNP) {
+                    // Character and char, etc.
+                    return CONVERSION_DIFFICULTY_REFLECTION;
+                }
+            } else {  // formal is non-primitive
+                if (actual == Null.class) {
+                    return CONVERSION_DIFFICULTY_REFLECTION;
+                }
+                
+                formalNP = formal;
+            }
+            if (Number.class.isAssignableFrom(actual) && Number.class.isAssignableFrom(formalNP)) {
+                return OverloadedNumberUtil.getArgumentConversionPrice(actual, formalNP) == Integer.MAX_VALUE
+                        ? CONVERSION_DIFFICULTY_IMPOSSIBLE : CONVERSION_DIFFICULTY_REFLECTION;
+            } else if (formal.isArray()) {
+                // DefaultObjectWrapper method/constructor calls convert from List to array automatically
+                return List.class.isAssignableFrom(actual)
+                        ? CONVERSION_DIFFICULTY_FREEMARKER : CONVERSION_DIFFICULTY_IMPOSSIBLE;
+            } else if (actual.isArray() && formal.isAssignableFrom(List.class)) {
+                // DefaultObjectWrapper method/constructor calls convert from array to List automatically
+                return CONVERSION_DIFFICULTY_FREEMARKER;
+            } else if (actual == CharacterOrString.class
+                    && (formal.isAssignableFrom(String.class)
+                            || formal.isAssignableFrom(Character.class) || formal == char.class)) {
+                return CONVERSION_DIFFICULTY_FREEMARKER;
+            } else {
+                return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+            }
+        }
+    }
+    
+    /**
+     * Symbolizes the class of null (it's missing from Java).
+     */
+    private static class Null {
+        
+        // Can't be instantiated
+        private Null() { }
+        
+    }
+    
+    /**
+     * Used instead of {@link ReflectionCallableMemberDescriptor} when the method is only applicable
+     * ({@link #isApplicable}) with conversion that Java reflection won't do. It delegates to a
+     * {@link ReflectionCallableMemberDescriptor}, but it adds the necessary conversions to the invocation methods. 
+     */
+    private static final class SpecialConversionCallableMemberDescriptor extends CallableMemberDescriptor {
+        
+        private final ReflectionCallableMemberDescriptor callableMemberDesc;
+
+        SpecialConversionCallableMemberDescriptor(ReflectionCallableMemberDescriptor callableMemberDesc) {
+            this.callableMemberDesc = callableMemberDesc;
+        }
+
+        @Override
+        TemplateModel invokeMethod(DefaultObjectWrapper ow, Object obj, Object[] args) throws TemplateModelException,
+                InvocationTargetException, IllegalAccessException {
+            convertArgsToReflectionCompatible(ow, args);
+            return callableMemberDesc.invokeMethod(ow, obj, args);
+        }
+
+        @Override
+        Object invokeConstructor(DefaultObjectWrapper ow, Object[] args) throws IllegalArgumentException,
+                InstantiationException, IllegalAccessException, InvocationTargetException, TemplateModelException {
+            convertArgsToReflectionCompatible(ow, args);
+            return callableMemberDesc.invokeConstructor(ow, args);
+        }
+
+        @Override
+        String getDeclaration() {
+            return callableMemberDesc.getDeclaration();
+        }
+
+        @Override
+        boolean isConstructor() {
+            return callableMemberDesc.isConstructor();
+        }
+
+        @Override
+        boolean isStatic() {
+            return callableMemberDesc.isStatic();
+        }
+
+        @Override
+        boolean isVarargs() {
+            return callableMemberDesc.isVarargs();
+        }
+
+        @Override
+        Class<?>[] getParamTypes() {
+            return callableMemberDesc.getParamTypes();
+        }
+        
+        @Override
+        String getName() {
+            return callableMemberDesc.getName();
+        }
+
+        private void convertArgsToReflectionCompatible(DefaultObjectWrapper ow, Object[] args) throws TemplateModelException {
+            Class<?>[] paramTypes = callableMemberDesc.getParamTypes();
+            int ln = paramTypes.length;
+            for (int i = 0; i < ln; i++) {
+                Class<?> paramType = paramTypes[i];
+                final Object arg = args[i];
+                if (arg == null) continue;
+                
+                // Handle conversion between List and array types, in both directions. Java reflection won't do such
+                // conversion, so we have to.
+                // Most reflection-incompatible conversions were already addressed by the unwrapping. The reason
+                // this one isn't is that for overloaded methods the hint of a given parameter position is often vague,
+                // so we may end up with a List even if some parameter types at that position are arrays (remember, we
+                // have to chose one unwrapping target type, despite that we have many possible overloaded methods), or
+                // the other way around (that happens when AdapterTemplateMoldel returns an array).
+                // Later, the overloaded method selection will assume that a List argument is applicable to an array
+                // parameter, and that an array argument is applicable to a List parameter, so we end up with this
+                // situation.
+                if (paramType.isArray() && arg instanceof List) {
+                   args[i] = ow.listToArray((List<?>) arg, paramType, null);
+                }
+                if (arg.getClass().isArray() && paramType.isAssignableFrom(List.class)) {
+                    args[i] = ow.arrayToList(arg);
+                }
+                
+                // Handle the conversion from CharacterOrString to Character or String:
+                if (arg instanceof CharacterOrString) {
+                    if (paramType == Character.class || paramType == char.class
+                            || (!paramType.isAssignableFrom(String.class)
+                                    && paramType.isAssignableFrom(Character.class))) {
+                        args[i] = Character.valueOf(((CharacterOrString) arg).getAsChar());
+                    } else {
+                        args[i] = ((CharacterOrString) arg).getAsString();
+                    }
+                }
+            }
+        }
+
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ArrayModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ArrayModel.java b/src/main/java/org/apache/freemarker/core/model/impl/ArrayModel.java
new file mode 100644
index 0000000..12ef9f0
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ArrayModel.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Array;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+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.TemplateSequenceModel;
+
+/**
+ * <p>A class that will wrap an arbitrary array into {@link TemplateCollectionModel}
+ * and {@link TemplateSequenceModel} interfaces. It supports element retrieval through the <tt>array[index]</tt>
+ * syntax and can be iterated as a list.
+ */
+public class ArrayModel
+extends
+    BeanModel
+implements
+    TemplateCollectionModel,
+    TemplateSequenceModel {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new ArrayModel(object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+        
+    // Cached length of the array
+    private final int length;
+
+    /**
+     * Creates a new model that wraps the specified array object.
+     * @param array the array object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     * @throws IllegalArgumentException if the passed object is not a Java array.
+     */
+    public ArrayModel(Object array, DefaultObjectWrapper wrapper) {
+        super(array, wrapper);
+        Class clazz = array.getClass();
+        if (!clazz.isArray())
+            throw new IllegalArgumentException("Object is not an array, it's " + array.getClass().getName());
+        length = Array.getLength(array);
+    }
+
+
+    @Override
+    public TemplateModelIterator iterator() {
+        return new Iterator();
+    }
+
+    @Override
+    public TemplateModel get(int index)
+    throws TemplateModelException {
+        try {
+            return wrap(Array.get(object, index));
+        } catch (IndexOutOfBoundsException e) {
+            return null; 
+//            throw new TemplateModelException("Index out of bounds: " + index);
+        }
+    }
+
+    private class Iterator
+    implements 
+        TemplateSequenceModel,
+        TemplateModelIterator {
+        private int position = 0;
+
+        @Override
+        public boolean hasNext() {
+            return position < length;
+        }
+
+        @Override
+        public TemplateModel get(int index)
+        throws TemplateModelException {
+            return ArrayModel.this.get(index);
+        }
+
+        @Override
+        public TemplateModel next()
+        throws TemplateModelException {
+            return position < length ? get(position++) : null;
+        }
+
+        @Override
+        public int size() {
+            return ArrayModel.this.size();
+        }
+    }
+
+    @Override
+    public int size() {
+        return length;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return length == 0;
+    }
+}


[05/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/ParameterListPreferabilityTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/ParameterListPreferabilityTest.java b/src/test/java/org/apache/freemarker/core/model/impl/ParameterListPreferabilityTest.java
new file mode 100644
index 0000000..2c22dc6
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ParameterListPreferabilityTest.java
@@ -0,0 +1,445 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.freemarker.core.util._NumberUtil;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("boxing")
+public class ParameterListPreferabilityTest extends TestCase {
+
+    public ParameterListPreferabilityTest(String name) {
+        super(name);
+    }
+    
+    public void testNumberical() {
+        // Note: the signature lists consists of the same elements, only their order changes depending on the type
+        // of the argument value.
+        
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { byte.class },
+                    new Class[] { Byte.class },
+                    new Class[] { short.class },
+                    new Class[] { Short.class },
+                    new Class[] { int.class },
+                    new Class[] { Integer.class },
+                    new Class[] { long.class },
+                    new Class[] { Long.class },
+                    new Class[] { BigInteger.class },
+                    new Class[] { float.class },
+                    new Class[] { Float.class },
+                    new Class[] { double.class },
+                    new Class[] { Double.class },
+                    new Class[] { BigDecimal.class },
+                    new Class[] { Number.class },
+                    new Class[] { Serializable.class },
+                    new Class[] { Object.class }
+                },
+                new Object[] { (byte) 1 });
+        
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { BigDecimal.class },
+                    new Class[] { BigInteger.class },
+                    new Class[] { int.class },
+                    new Class[] { Integer.class },
+                    new Class[] { long.class },
+                    new Class[] { Long.class },
+                    new Class[] { double.class },
+                    new Class[] { Double.class },
+                    new Class[] { float.class },
+                    new Class[] { Float.class },
+                    new Class[] { short.class },
+                    new Class[] { Short.class },
+                    new Class[] { byte.class },
+                    new Class[] { Byte.class },
+                    new Class[] { Number.class },
+                    new Class[] { Serializable.class },
+                    new Class[] { Object.class },
+                },
+                new Object[] { new OverloadedNumberUtil.IntegerBigDecimal(new BigDecimal("1")) });
+
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { BigDecimal.class },
+                    new Class[] { double.class },
+                    new Class[] { Double.class },
+                    new Class[] { float.class },
+                    new Class[] { Float.class },
+                    new Class[] { BigInteger.class },
+                    new Class[] { int.class },
+                    new Class[] { Integer.class },
+                    new Class[] { long.class },
+                    new Class[] { Long.class },
+                    new Class[] { short.class },
+                    new Class[] { Short.class },
+                    new Class[] { byte.class },
+                    new Class[] { Byte.class },
+                    new Class[] { Number.class },
+                    new Class[] { Serializable.class },
+                    new Class[] { Object.class },
+                },
+                new Object[] { new BigDecimal("1") /* possibly non-integer */ });
+        
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { float.class },
+                    new Class[] { Float.class },
+                    new Class[] { double.class },
+                    new Class[] { Double.class },
+                    new Class[] { BigDecimal.class },
+                    new Class[] { int.class },
+                    new Class[] { Integer.class },
+                    new Class[] { long.class },
+                    new Class[] { Long.class },
+                    new Class[] { short.class },
+                    new Class[] { Short.class },
+                    new Class[] { byte.class },
+                    new Class[] { Byte.class },
+                    new Class[] { BigInteger.class },
+                    new Class[] { Number.class },
+                    new Class[] { Serializable.class },
+                    new Class[] { Object.class },
+                },
+                new Object[] { new OverloadedNumberUtil.FloatOrByte(1f, (byte) 1) });
+        
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { int.class },
+                    new Class[] { Integer.class },
+                    new Class[] { long.class },
+                    new Class[] { Long.class },
+                    new Class[] { BigInteger.class },
+                    new Class[] { double.class },
+                    new Class[] { Double.class },
+                    new Class[] { BigDecimal.class },
+                    new Class[] { short.class },
+                    new Class[] { Short.class },
+                    new Class[] { float.class },
+                    new Class[] { Float.class },
+                    
+                    // Two incompatibles removed, would be removed in reality:
+                    new Class[] { byte.class },
+                    new Class[] { Byte.class },
+                    
+                    new Class[] { Number.class },
+                    new Class[] { Serializable.class },
+                    new Class[] { Object.class }
+                },
+                new Object[] { new OverloadedNumberUtil.IntegerOrShort(1, (short) 1) });
+        
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { long.class },
+                    new Class[] { Long.class },
+                    new Class[] { BigInteger.class },
+                    new Class[] { BigDecimal.class },
+                    new Class[] { double.class },
+                    new Class[] { Double.class },
+                    new Class[] { float.class },
+                    new Class[] { Float.class },
+                    // skip byte and short  as the would be equal with int (all invalid target types)
+                    new Class[] { int.class },  // In reality, this and Integer are removed as not-applicable overloads
+                    new Class[] { Integer.class },
+                    new Class[] { Number.class },
+                    new Class[] { Serializable.class },
+                    new Class[] { Object.class }
+                },
+                new Object[] { 1L });
+        
+        // Undecidable comparisons:
+        
+        testAllCmpPermutationsEqu(
+                new Class[][] {
+                        new Class[] { Byte.class },
+                        new Class[] { Short.class },
+                        new Class[] { Integer.class },
+                        new Class[] { Long.class },
+                        new Class[] { BigInteger.class },
+                        new Class[] { Float.class },
+                    },
+                    new Object[] { 1.0 });
+        
+        testAllCmpPermutationsEqu(
+                new Class[][] {
+                        new Class[] { byte.class },
+                        new Class[] { short.class },
+                        new Class[] { int.class },
+                        new Class[] { long.class },
+                        new Class[] { float.class },
+                    },
+                    new Object[] { 1.0 });
+    }
+    
+    public void testPrimitiveIsMoreSpecific() {
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { boolean.class },
+                    new Class[] { Boolean.class }
+                },
+                new Object[] { true });
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { char.class },
+                    new Class[] { Character.class }
+                },
+                new Object[] { 'x' });
+    }
+    
+    public void testCharIsMoreSpecificThanString() {
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { char.class },
+                    new Class[] { Character.class },
+                    new Class[] { String.class },
+                    new Class[] { CharSequence.class }
+                },
+                new Object[] { "s" });
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { char.class },
+                    new Class[] { Character.class },
+                    new Class[] { String.class }
+                },
+                new Object[] { 'c' });
+    }
+    
+    public void testClassHierarchy() {
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { LinkedHashMap.class },
+                    new Class[] { HashMap.class },
+                    new Class[] { Map.class },
+                    new Class[] { Object.class }
+                },
+                new Object[] { new LinkedHashMap() });
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { LinkedHashMap.class },
+                    new Class[] { Cloneable.class },
+                    new Class[] { Object.class }
+                },
+                new Object[] { new LinkedHashMap() });
+    }
+
+    public void testNumericalWithNonNumerical() {
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { int.class },
+                    new Class[] { Integer.class },
+                    new Class[] { Comparable.class },
+                    new Class[] { Object.class },
+                },
+                new Object[] { 1 });
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { int.class },
+                    new Class[] { Integer.class },
+                    new Class[] { char.class },
+                    new Class[] { Character.class },
+                },
+                new Object[] { 1 });
+    }
+    
+    public void testUnrelated() {
+        testAllCmpPermutationsEqu(
+                new Class[][] {
+                    new Class[] { Serializable.class },
+                    new Class[] { CharSequence.class },
+                    new Class[] { Comparable.class }
+                },
+                new Object[] { "s" });
+        
+        testAllCmpPermutationsEqu(
+                new Class[][] {
+                    new Class[] { HashMap.class },
+                    new Class[] { TreeMap.class }
+                },
+                new Object[] { null });
+    }
+
+    public void testMultiParameter() {
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { String.class, String.class, String.class },
+                        
+                    new Class[] { String.class, String.class, Object.class },
+                    new Class[] { String.class, Object.class, String.class },
+                    new Class[] { Object.class, String.class, String.class },
+                    
+                    new Class[] { String.class, Object.class, Object.class },
+                    new Class[] { Object.class, String.class, Object.class },
+                    new Class[] { Object.class, Object.class, String.class },
+                    
+                    new Class[] { Object.class, Object.class, Object.class },
+                },
+                new Object[] { "a", "b", "c" });
+        
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { String.class, String.class },
+                    new Class[] { String.class, Object.class },
+                    new Class[] { CharSequence.class, CharSequence.class },
+                    new Class[] { CharSequence.class, Object.class },
+                    new Class[] { Object.class, String.class },
+                    new Class[] { Object.class, CharSequence.class },
+                    new Class[] { Object.class, Object.class },
+                },
+                new Object[] { "a", "b" });
+        
+        // Subclassing is more important than primitive-VS-boxed:
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { boolean.class, boolean.class, boolean.class, String.class },
+                    new Class[] { Boolean.class, boolean.class, boolean.class, String.class },
+                    new Class[] { boolean.class, Boolean.class, Boolean.class, String.class },
+                    new Class[] { Boolean.class, boolean.class, Boolean.class, String.class },
+                    new Class[] { Boolean.class, Boolean.class, boolean.class, String.class },
+                    new Class[] { Boolean.class, Boolean.class, Boolean.class, String.class },
+                    new Class[] { Boolean.class, Boolean.class, Boolean.class, CharSequence.class },
+                    new Class[] { boolean.class, boolean.class, boolean.class, Object.class },
+                    new Class[] { Boolean.class, boolean.class, boolean.class, Object.class },
+                    new Class[] { boolean.class, Boolean.class, Boolean.class, Object.class },
+                    new Class[] { Boolean.class, boolean.class, Boolean.class, Object.class },
+                    new Class[] { Boolean.class, Boolean.class, boolean.class, Object.class },
+                    new Class[] { Boolean.class, Boolean.class, Boolean.class, Object.class },
+                },
+                new Object[] { true, false, true, "a" });
+        
+        // Subclassing is more important than primitive-VS-boxed:
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { int.class, int.class, int.class, String.class },
+                    new Class[] { Integer.class, int.class, int.class, String.class },
+                    new Class[] { int.class, Integer.class, Integer.class, String.class },
+                    new Class[] { Integer.class, int.class, Integer.class, String.class },
+                    new Class[] { Integer.class, Integer.class, int.class, String.class },
+                    new Class[] { Integer.class, Integer.class, Integer.class, String.class },
+                    new Class[] { Integer.class, Integer.class, Integer.class, CharSequence.class },
+                    new Class[] { int.class, int.class, int.class, Object.class },
+                    new Class[] { Integer.class, int.class, int.class, Object.class },
+                    new Class[] { int.class, Integer.class, Integer.class, Object.class },
+                    new Class[] { Integer.class, int.class, Integer.class, Object.class },
+                    new Class[] { Integer.class, Integer.class, int.class, Object.class },
+                    new Class[] { Integer.class, Integer.class, Integer.class, Object.class },
+                },
+                new Object[] { 1, 2, 3, "a" });
+    }
+
+    public void testVarargs() {
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { String.class, String[].class },
+                    new Class[] { String.class, CharSequence[].class },
+                    new Class[] { String.class, Object[].class },
+                },
+                new Object[] { "a", "b", "c" },
+                true);
+        
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { String.class, int[].class },
+                    new Class[] { String.class, Integer[].class },
+                    new Class[] { String.class, long[].class },
+                    new Class[] { String.class, Long[].class },
+                    new Class[] { String.class, double[].class },
+                    new Class[] { String.class, Double[].class },
+                    new Class[] { String.class, Serializable[].class },
+                    new Class[] { String.class, Object[].class },
+                },
+                new Object[] { "a", 1, 2, 3 },
+                true);
+        
+        // 0-long varargs list; in case of ambiguity, the varargs component type decides:
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { String.class, Object[].class },
+                    new Class[] { CharSequence.class, int[].class },
+                    new Class[] { CharSequence.class, Integer[].class },
+                    new Class[] { CharSequence.class, long[].class },
+                    new Class[] { CharSequence.class, Long[].class },
+                    new Class[] { CharSequence.class, double[].class },
+                    new Class[] { CharSequence.class, Double[].class },
+                    new Class[] { CharSequence.class, Serializable[].class },
+                    new Class[] { CharSequence.class, Object[].class },
+                    new Class[] { Object.class, int[].class },
+                },
+                new Object[] { "a" },
+                true);
+        
+        
+        // Different oms prefix length; in the case of ambiguity, the one with higher oms param count wins.
+        testAllCmpPermutationsInc(
+                new Class[][] {
+                    new Class[] { String.class, int.class, int.class, int[].class },
+                    new Class[] { String.class, int.class, int[].class },
+                    new Class[] { String.class, int[].class },
+                },
+                new Object[] { "a", 1, 2, 3 },
+                true);
+    }
+    
+    private void testAllCmpPermutationsInc(Class[][] sortedSignatures, Object[] args) {
+        testAllCmpPermutationsInc(sortedSignatures, args, false);
+    }
+
+    /**
+     * Compares all items with all other items in the provided descending sorted array of signatures, checking that
+     * for all valid indexes i and j, where j > i, it stands that sortedSignatures[i] > sortedSignatures[j].
+     * The comparisons are done with both operand orders, also each items is compared to itself too.
+     * 
+     * @param sortedSignatures method signatures sorted by decreasing specificity
+     */
+    private void testAllCmpPermutationsInc(Class[][] sortedSignatures, Object[] args, boolean varargs) {
+        final ArgumentTypes argTs = new ArgumentTypes(args);
+        for (int i = 0; i < sortedSignatures.length; i++) {
+            for (int j = 0; j < sortedSignatures.length; j++) {
+                assertEquals("sortedSignatures[" + i + "] <==> sortedSignatures [" + j + "]",
+                        _NumberUtil.getSignum(
+                                Integer.valueOf(j).compareTo(i)),
+                        _NumberUtil.getSignum(
+                                argTs.compareParameterListPreferability(
+                                        sortedSignatures[i], sortedSignatures[j], varargs)));
+            }
+        }
+    }
+
+    private void testAllCmpPermutationsEqu(Class[][] signatures, Object[] args) {
+        final ArgumentTypes argTs = new ArgumentTypes(args);
+        for (int i = 0; i < signatures.length; i++) {
+            for (int j = 0; j < signatures.length; j++) {
+                assertEquals("sortedSignatures[" + i + "] <==> sortedSignatures [" + j + "]",
+                        0,
+                        argTs.compareParameterListPreferability(signatures[i], signatures[j], false));
+            }
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/PrallelObjectIntrospectionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/PrallelObjectIntrospectionTest.java b/src/test/java/org/apache/freemarker/core/model/impl/PrallelObjectIntrospectionTest.java
new file mode 100644
index 0000000..dadefba
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/PrallelObjectIntrospectionTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+public class PrallelObjectIntrospectionTest extends AbstractParallelIntrospectionTest {
+
+    public PrallelObjectIntrospectionTest(String name) {
+        super(name);
+    }
+
+    public static void main(String[] args) {
+        new PrallelObjectIntrospectionTest("almostForever")
+                .testReliability(Integer.MAX_VALUE);
+    }
+    
+    @Override
+    protected TemplateHashModel getWrappedEntity(int objIdx)
+    throws TemplateModelException {
+        return (TemplateHashModel) getObjectWrapper().wrap(
+                ManyObjectsOfDifferentClasses.OBJECTS[objIdx]);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/PrallelStaticIntrospectionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/PrallelStaticIntrospectionTest.java b/src/test/java/org/apache/freemarker/core/model/impl/PrallelStaticIntrospectionTest.java
new file mode 100644
index 0000000..0cac78a
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/PrallelStaticIntrospectionTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+public class PrallelStaticIntrospectionTest extends AbstractParallelIntrospectionTest {
+
+    private static final String STATICS_CLASS_CONTAINER_CLASS_NAME
+            = ManyStaticsOfDifferentClasses.class.getName();
+
+    public PrallelStaticIntrospectionTest(String name) {
+        super(name);
+    }
+
+    public static void main(String[] args) {
+        new PrallelStaticIntrospectionTest("almostForever")
+                .testReliability(Integer.MAX_VALUE);
+    }
+    
+    @Override
+    protected TemplateHashModel getWrappedEntity(int clIdx)
+    throws TemplateModelException {
+        return (TemplateHashModel) getObjectWrapper().getStaticModels().get(
+                STATICS_CLASS_CONTAINER_CLASS_NAME + "$C"
+                + clIdx);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/RationalNumber.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/RationalNumber.java b/src/test/java/org/apache/freemarker/core/model/impl/RationalNumber.java
new file mode 100644
index 0000000..7359439
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/RationalNumber.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+public final class RationalNumber extends Number {
+    
+    final int divident;
+    final int divisor;
+    
+    public RationalNumber(int divident, int divisor) {
+        this.divident = divident;
+        this.divisor = divisor;
+    }
+
+    @Override
+    public int intValue() {
+        return divident / divisor;
+    }
+
+    @Override
+    public long longValue() {
+        return divident / (long) divisor;
+    }
+
+    @Override
+    public float floatValue() {
+        return (float) (divident / (double) divisor);
+    }
+
+    @Override
+    public double doubleValue() {
+        return divident / (double) divisor;
+    }
+
+    public int getDivident() {
+        return divident;
+    }
+
+    public int getDivisor() {
+        return divisor;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + divident;
+        result = prime * result + divisor;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        RationalNumber other = (RationalNumber) obj;
+        if (divident != other.divident)
+            return false;
+        if (divisor != other.divisor)
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return divident + "/" + divisor;
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..0287d6d
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/StaticModelsTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class StaticModelsTest {
+
+    @Test
+    public void modelCaching() throws Exception {
+        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+        TemplateHashModel statics = ow.getStaticModels();
+        TemplateHashModel s = (TemplateHashModel) statics.get(S.class.getName());
+        assertNotNull(s);
+        assertNotNull(s.get("F"));
+        assertNotNull(s.get("m"));
+        try {
+            s.get("x");
+            fail();
+        } catch (TemplateModelException e) {
+            assertThat(e.getMessage(), containsString("No such key"));
+        }
+        
+        try {
+            statics.get("no.such.ClassExists");
+            fail();
+        } catch (TemplateModelException e) {
+            assertTrue(e.getCause() instanceof ClassNotFoundException);
+        }
+        
+        TemplateModel f = s.get("F");
+        assertTrue(f instanceof TemplateScalarModel);
+        assertEquals(((TemplateScalarModel) f).getAsString(), "F OK");
+        
+        TemplateModel m = s.get("m");
+        assertTrue(m instanceof TemplateMethodModelEx);
+        assertEquals(((TemplateScalarModel) ((TemplateMethodModelEx) m).exec(new ArrayList())).getAsString(), "m OK");
+        
+        assertSame(s, statics.get(S.class.getName()));
+        
+        ow.clearClassIntrospecitonCache();
+        TemplateHashModel sAfterClean = (TemplateHashModel) statics.get(S.class.getName());
+        assertNotSame(s, sAfterClean);
+        assertSame(sAfterClean, statics.get(S.class.getName()));
+        assertNotNull(sAfterClean.get("F"));
+        assertNotNull(sAfterClean.get("m"));
+    }
+    
+    public static class S {
+        
+        public static final String F = "F OK"; 
+        
+        public static String m() {
+            return "m OK";
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..856a69f
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/TypeFlagsTest.java
@@ -0,0 +1,671 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.freemarker.core.Configuration;
+
+import junit.framework.TestCase;
+
+public class TypeFlagsTest extends TestCase {
+
+    public TypeFlagsTest(String name) {
+        super(name);
+    }
+    
+    private final DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+
+    public void testSingleNumType() {
+        checkTypeFlags(SingleNumTypeC.class, "mInt",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.INTEGER | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                | TypeFlags.ACCEPTS_STRING);
+        checkTypeFlags(SingleNumTypeC.class, "mLong",
+                TypeFlags.LONG | TypeFlags.ACCEPTS_NUMBER);
+        checkTypeFlags(SingleNumTypeC.class, "mShort",
+                TypeFlags.SHORT | TypeFlags.ACCEPTS_NUMBER);
+        checkTypeFlags(SingleNumTypeC.class, "mByte",
+                TypeFlags.BYTE | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.ACCEPTS_ANY_OBJECT);
+        checkTypeFlags(SingleNumTypeC.class, "mDouble",
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER);
+        checkTypeFlags(SingleNumTypeC.class, "mFloat",
+                TypeFlags.FLOAT | TypeFlags.ACCEPTS_NUMBER);
+        checkTypeFlags(SingleNumTypeC.class, "mUnknown",
+                TypeFlags.UNKNOWN_NUMERICAL_TYPE | TypeFlags.ACCEPTS_NUMBER);
+        
+        checkTypeFlags(SingleNumTypeC.class, "mVarParamCnt",
+                TypeFlags.BIG_DECIMAL | TypeFlags.ACCEPTS_NUMBER);
+        checkTypeFlags(SingleNumTypeC.class, "mVarParamCnt",
+                TypeFlags.BIG_INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER);
+        checkTypeFlags(SingleNumTypeC.class, "mVarParamCnt",
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.FLOAT | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER);
+    }
+    
+    static public class SingleNumTypeC {
+        public void mInt(int a1, String a2) { }
+        public void mInt(int a1, int a2) { }
+        public void mLong(long a1) { }
+        public void mLong(Long a1) { }
+        public void mShort(short a1) { }
+        public void mByte(byte a1, boolean a2) { }
+        public void mByte(byte a1, String a2) { }
+        public void mByte(byte a1, Object a2) { }
+        public void mDouble(double a1) { }
+        public void mFloat(float a1) { }
+        public void mUnknown(RationalNumber a1) { };
+        
+        public void mVarParamCnt(BigDecimal a1) { }
+        public void mVarParamCnt(BigInteger a1, Double a2) { }
+        public void mVarParamCnt(Double a1,     Float a2, Integer a3) { }
+        public void mVarParamCnt(Object a1,     char a2,  boolean a3, File a4, Map a5,    Boolean a6) { }
+        public void mVarParamCnt(Long a1,       int a2,   short a3,   byte a4, double a5, float a6) { }
+    }
+
+    public void testMultipleNumTypes() {
+        checkTypeFlags(MultiNumTypeC.class, "m1",
+                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT 
+                | TypeFlags.BYTE | TypeFlags.DOUBLE | TypeFlags.INTEGER
+                | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(MultiNumTypeC.class, "m2",
+                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT 
+                | TypeFlags.SHORT | TypeFlags.LONG | TypeFlags.FLOAT
+                | TypeFlags.ACCEPTS_NUMBER | TypeFlags.CHARACTER
+                );
+
+        checkTypeFlags(MultiNumTypeC.class, "m3",
+                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT 
+                | TypeFlags.BIG_DECIMAL | TypeFlags.BIG_INTEGER
+                | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.BIG_INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT
+                | TypeFlags.BIG_DECIMAL | TypeFlags.UNKNOWN_NUMERICAL_TYPE
+                | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(MultiNumTypeC.class, "m4",
+                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.FLOAT | TypeFlags.ACCEPTS_NUMBER
+                | TypeFlags.CHARACTER
+                );
+        
+        checkTypeFlags(MultiNumTypeC.class, "m5",
+                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT
+                | TypeFlags.FLOAT | TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(MultiNumTypeC.class, "m6",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER
+                );
+        assertSame(getTypeFlags(MultiNumTypeC.class, "m6", false, 2), OverloadedMethodsSubset.ALL_ZEROS_ARRAY);
+        assertSame(getTypeFlags(MultiNumTypeC.class, "m6", true, 2), OverloadedMethodsSubset.ALL_ZEROS_ARRAY);
+        assertSame(getTypeFlags(MultiNumTypeC.class, "m6", false, 3), OverloadedMethodsSubset.ALL_ZEROS_ARRAY);
+        assertSame(getTypeFlags(MultiNumTypeC.class, "m6", true, 3), OverloadedMethodsSubset.ALL_ZEROS_ARRAY);
+        checkTypeFlags(MultiNumTypeC.class, "m6",
+                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
+                0
+                );
+    }
+    
+    static public class MultiNumTypeC {
+        public void m1(byte a1) { };
+        public void m1(int a1) { };
+        public void m1(double a2) { };
+        
+        public void m2(short a1) { };
+        public void m2(long a1) { };
+        public void m2(float a1) { };
+        public void m2(char a1) { };
+        
+        public void m3(BigInteger a1, BigInteger a2, BigDecimal a3) { };
+        public void m3(BigDecimal a1, BigInteger a2, RationalNumber a3) { };
+        
+        public void m4(float a1) { };
+        public void m4(char a1) { };
+        
+        public void m5(Float a1) { };
+        public void m5(Double a1) { };
+        public void m5(Enum a1) { };
+        
+        public void m6(int a1) { };
+        public void m6(File a1, Throwable a2) { };
+        public void m6(File a1, Throwable a2, StringBuilder a3) { };
+        public void m6(File a1, Throwable a2, Throwable a3) { };
+        public void m6(double a1, int a2, File a3, File a4) { };
+        public void m6(File a1, int a2, double a3, File a4) { };
+    }
+
+    public void testVarargsNums() {
+        checkTypeFlags(VarArgsC.class, "m1",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER
+                );
+        checkTypeFlags(VarArgsC.class, "m2",
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m3",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER
+                );
+        checkTypeFlags(VarArgsC.class, "m3",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.INTEGER
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+        checkTypeFlags(VarArgsC.class, "m3",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.INTEGER
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.INTEGER
+                | TypeFlags.BIG_DECIMAL | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m4",
+                TypeFlags.INTEGER | TypeFlags.LONG
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m5",
+                TypeFlags.LONG | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m6",
+                TypeFlags.LONG | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                | TypeFlags.ACCEPTS_STRING
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m7",
+                TypeFlags.INTEGER | TypeFlags.BYTE
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.FLOAT
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+
+        checkTypeFlags(VarArgsC.class, "m8",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.FLOAT
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m9",
+                TypeFlags.INTEGER | TypeFlags.BYTE
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m10",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER
+                );
+        checkTypeFlags(VarArgsC.class, "m10",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.LONG | TypeFlags.DOUBLE
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m11",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.SHORT | TypeFlags.ACCEPTS_NUMBER
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+        checkTypeFlags(VarArgsC.class, "m11",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.SHORT
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.LONG | TypeFlags.DOUBLE
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m12",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER
+                );
+        checkTypeFlags(VarArgsC.class, "m12",
+                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.SHORT
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.BYTE
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.LONG | TypeFlags.DOUBLE
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+        
+        checkTypeFlags(VarArgsC.class, "m13",
+                TypeFlags.CHARACTER,
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER);
+        checkTypeFlags(VarArgsC.class, "m13",
+                TypeFlags.CHARACTER,
+                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.UNKNOWN_NUMERICAL_TYPE
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
+                TypeFlags.DOUBLE | TypeFlags.LONG
+                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
+                );
+    }
+    
+    static public class VarArgsC {
+        public void m1(int... va) { }
+        
+        public void m2(double a1, int... va) { }
+        
+        public void m3(int... va) { }
+        public void m3(int a1, double... va) { }
+        public void m3(int a1, double a2, BigDecimal... va) { }
+        
+        public void m4(int... va) { }
+        public void m4(long... va) { }
+
+        public void m5(Long... va) { }
+        public void m5(long... va) { }
+        
+        public void m6(long... va) { }
+        public void m6(String... va) { }
+        
+        public void m7(int a1, double... va) { }
+        public void m7(byte a1, float... va) { }
+
+        public void m8(int a1, double... va) { }
+        public void m8(int a1, float... va) { }
+        
+        public void m9(int a1, double... va) { }
+        public void m9(byte a1, double... va) { }
+        
+        public void m10(int a1, double a2, long... va) { }
+        public void m10(int a1, double... va) { }
+        
+        public void m11(int a1, short a2, long... va) { }
+        public void m11(int a1, double... va) { }
+        
+        public void m12(int a1, short a2, byte a3, long... va) { }
+        public void m12(int a1, double... va) { }
+        
+        public void m13(char a1, double a2, RationalNumber a3, Long... va) { }
+        public void m13(char a1, Double... va) { }
+    }
+    
+    public void testAllZeros() {
+        for (boolean reverse : new boolean[] { true, false }) {
+            assertSame(OverloadedMethodsSubset.ALL_ZEROS_ARRAY, getTypeFlags(AllZeroC.class, "m1", reverse, 0));
+            assertSame(OverloadedMethodsSubset.ALL_ZEROS_ARRAY, getTypeFlags(AllZeroC.class, "m2", reverse, 2));
+            assertSame(OverloadedMethodsSubset.ALL_ZEROS_ARRAY, getTypeFlags(AllZeroC.class, "m3", reverse, 1));
+        }
+    }
+    
+    static public class AllZeroC {
+        public void m1() {}
+        
+        public void m2(File a1, File a2) {}
+        
+        public void m3(File a1) {}
+        public void m3(StringBuilder a1) {}
+    }
+
+    public void testAcceptanceNonOverloaded() {
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber1",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.UNKNOWN_NUMERICAL_TYPE);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber2",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.BYTE);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber3",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.BYTE);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber4",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.SHORT);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber5",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.SHORT);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber6",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.INTEGER);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber7",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.INTEGER);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber8",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.LONG);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber9",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.LONG);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber10", TypeFlags.ACCEPTS_NUMBER | TypeFlags.FLOAT);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber11", TypeFlags.ACCEPTS_NUMBER | TypeFlags.FLOAT);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber12", TypeFlags.ACCEPTS_NUMBER | TypeFlags.DOUBLE);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber13", TypeFlags.ACCEPTS_NUMBER | TypeFlags.DOUBLE);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber14", TypeFlags.ACCEPTS_NUMBER | TypeFlags.BIG_INTEGER);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber15", TypeFlags.ACCEPTS_NUMBER | TypeFlags.BIG_DECIMAL);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber16", TypeFlags.ACCEPTS_NUMBER | TypeFlags.UNKNOWN_NUMERICAL_TYPE);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mDate", TypeFlags.ACCEPTS_DATE);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mSQLDate1", 0);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mSQLDate2", 0);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mString", TypeFlags.ACCEPTS_STRING);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mCharSequence", TypeFlags.ACCEPTS_STRING);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mStringBuilder", 0);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mBool", TypeFlags.ACCEPTS_BOOLEAN);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mBoolean", TypeFlags.ACCEPTS_BOOLEAN);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mMap1", TypeFlags.ACCEPTS_MAP);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mMap2", 0);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mList1", TypeFlags.ACCEPTS_LIST);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mList2", 0);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mSet1", TypeFlags.ACCEPTS_SET);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mSet2", 0);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mCollection", TypeFlags.ACCEPTS_SET | TypeFlags.ACCEPTS_LIST);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mChar1", TypeFlags.CHARACTER);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mChar2", TypeFlags.CHARACTER);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mArray1", TypeFlags.ACCEPTS_ARRAY);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mArray2", TypeFlags.ACCEPTS_ARRAY);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mArray3", TypeFlags.ACCEPTS_ARRAY);
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mArray4", TypeFlags.ACCEPTS_ARRAY);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mObject", TypeFlags.ACCEPTS_ANY_OBJECT);
+        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_NUMBER) != 0);
+        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_STRING) != 0);
+        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_BOOLEAN) != 0);
+        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_MAP) != 0);
+        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_LIST) != 0);
+        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_SET) != 0);
+        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_ARRAY) != 0);
+        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.CHARACTER) == 0);  // deliberatly 0 
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mMapDate", 0);
+        
+        checkTypeFlags(AcceptanceNonoverloadedC.class, "mDateBooleanList",
+                TypeFlags.ACCEPTS_DATE, TypeFlags.ACCEPTS_BOOLEAN, TypeFlags.ACCEPTS_LIST);
+    }
+    
+    static public class AcceptanceNonoverloadedC {
+        public void mNumber1(Number a1) {}
+        public void mNumber2(Byte a1) {}
+        public void mNumber3(byte a1) {}
+        public void mNumber4(Short a1) {}
+        public void mNumber5(short a1) {}
+        public void mNumber6(Integer a1) {}
+        public void mNumber7(int a1) {}
+        public void mNumber8(Long a1) {}
+        public void mNumber9(long a1) {}
+        public void mNumber10(Float a1) {}
+        public void mNumber11(float a1) {}
+        public void mNumber12(Double a1) {}
+        public void mNumber13(double a1) {}
+        public void mNumber14(BigInteger a1) {}
+        public void mNumber15(BigDecimal a1) {}
+        public void mNumber16(RationalNumber a1) {}
+        
+        public void mDate(Date a1) {}
+        public void mSQLDate1(java.sql.Date a1) {}
+        public void mSQLDate2(java.sql.Timestamp a1) {}
+        
+        public void mString(String a1) {}
+        public void mCharSequence(CharSequence a1) {}
+        public void mStringBuilder(StringBuilder a1) {}
+        
+        public void mBool(boolean a1) {}
+        public void mBoolean(Boolean a1) {}
+
+        public void mMap1(Map a1) {}
+        public void mMap2(LinkedHashMap a1) {}
+        
+        public void mList1(List a1) {}
+        public void mList2(ArrayList a1) {}
+        
+        public void mSet1(Set a1) {}
+        public void mSet2(HashSet a1) {}
+        
+        public void mCollection(Collection a1) {}
+        
+        public void mMapDate(MapDate a1) {}
+
+        public void mChar1(Character a1) {}
+        public void mChar2(char a1) {}
+
+        public void mArray1(Object[] a1) {}
+        public void mArray2(int[] a1) {}
+        public void mArray3(Integer[] a1) {}
+        public void mArray4(Void[] a1) {}
+        
+        public void mObject(Object a1) {}
+        
+        public void mDateBooleanList(Date a1, boolean a2, List a3) {}
+    }
+
+    public void testAcceptanceOverloaded() {
+        checkTypeFlags(AcceptanceOverloadedC.class, "mLongDateList",
+                TypeFlags.ACCEPTS_NUMBER | TypeFlags.LONG | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT
+                | TypeFlags.ACCEPTS_DATE | TypeFlags.ACCEPTS_LIST);
+        checkTypeFlags(AcceptanceOverloadedC.class, "mBoolean", TypeFlags.ACCEPTS_BOOLEAN);
+        checkTypeFlags(AcceptanceOverloadedC.class, "mStringChar",
+                TypeFlags.ACCEPTS_STRING | TypeFlags.CHARACTER);
+        checkTypeFlags(AcceptanceOverloadedC.class, "mStringFile", TypeFlags.ACCEPTS_STRING);
+        checkTypeFlags(AcceptanceOverloadedC.class, "mMapObject", TypeFlags.ACCEPTS_ANY_OBJECT);
+        checkTypeFlags(AcceptanceOverloadedC.class, "mSetMap", TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_SET);
+        checkTypeFlags(AcceptanceOverloadedC.class, "mCollectionMap",
+                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_SET | TypeFlags.ACCEPTS_LIST);
+        checkTypeFlags(AcceptanceOverloadedC.class, "mArray", TypeFlags.ACCEPTS_ARRAY);
+        checkTypeFlags(AcceptanceOverloadedC.class, "mArrayList", TypeFlags.ACCEPTS_ARRAY | TypeFlags.ACCEPTS_LIST);
+        
+        checkTypeFlags(AcceptanceOverloadedC.class, "mStringCollectionThenBooleanThenMapList",
+                TypeFlags.ACCEPTS_STRING | TypeFlags.ACCEPTS_LIST | TypeFlags.ACCEPTS_SET,
+                TypeFlags.ACCEPTS_BOOLEAN,
+                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_LIST);
+    }
+    
+    static public class AcceptanceOverloadedC {
+        public void mLongDateList(long a1) {}
+        public void mLongDateList(Date a1) {}
+        public void mLongDateList(List a1) {}
+
+        public void mBoolean(boolean a1) {}
+        public void mBoolean(Boolean a1) {}
+        
+        public void mDate(Date a1) {}
+        public void mDate(java.sql.Date a1) {}
+        public void mDate(java.sql.Timestamp a1) {}
+        
+        public void mStringChar(String a1) {}
+        public void mStringChar(char a1) {}
+        public void mStringChar(Character a1) {}
+
+        public void mStringFile(String a1) {}
+        public void mStringFile(File a1) {}
+        
+        public void mMapObject(Map a1) {}
+        public void mMapObject(Object a1) {}
+        
+        public void mSetMap(Set a1) {}
+        public void mSetMap(Map a1) {}
+        
+        public void mCollectionMap(Collection a1) {}
+        public void mCollectionMap(Map a1) {}
+        
+        public void mArray(Object[] a1) {}
+        public void mArray(int[] a1) {}
+        public void mArray(Integer[] a1) {}
+        public void mArray(Void[] a1) {}
+        
+        public void mArrayList(String[] a1) {}
+        public void mArrayList(List a1) {}
+        
+        public void mStringCollectionThenBooleanThenMapList(String a1, boolean a2, Map a3) {}
+        public void mStringCollectionThenBooleanThenMapList(Collection a1, boolean a2, Map a3) {}
+        public void mStringCollectionThenBooleanThenMapList(String a1, boolean a2, List a3) {}
+    }
+
+    public void testAcceptanceVarargsC() {
+        checkTypeFlags(AcceptanceVarArgsC.class, "m1",
+                TypeFlags.ACCEPTS_LIST | TypeFlags.ACCEPTS_STRING);
+        
+        checkTypeFlags(AcceptanceVarArgsC.class, "m2",
+                TypeFlags.ACCEPTS_MAP,
+                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_STRING | TypeFlags.ACCEPTS_BOOLEAN,
+                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_STRING);
+        checkTypeFlags(AcceptanceVarArgsC.class, "m2",
+                TypeFlags.ACCEPTS_MAP,
+                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_STRING | TypeFlags.ACCEPTS_BOOLEAN);
+        checkTypeFlags(AcceptanceVarArgsC.class, "m2",
+                TypeFlags.ACCEPTS_MAP);
+        
+        checkTypeFlags(AcceptanceVarArgsC.class, "m3",
+                TypeFlags.ACCEPTS_BOOLEAN);
+        checkTypeFlags(AcceptanceVarArgsC.class, "m3",
+                TypeFlags.ACCEPTS_BOOLEAN | TypeFlags.ACCEPTS_STRING,
+                TypeFlags.ACCEPTS_BOOLEAN | TypeFlags.ACCEPTS_MAP,
+                TypeFlags.ACCEPTS_BOOLEAN | TypeFlags.CHARACTER,
+                TypeFlags.ACCEPTS_BOOLEAN);
+    }
+    
+    static public class AcceptanceVarArgsC {
+        public void m1(List... a1) {}
+        public void m1(String... a1) {}
+
+        public void m2(Map a1, String... a2) {}
+        public void m2(Map a1, boolean a2, String... a3) {}
+        public void m2(Map... a1) {}
+        
+        public void m3(String a1, Map a2, char a3, Boolean... a4) {}
+        public void m3(boolean... a1) {}
+    }
+
+    static public class MapDate extends Date implements Map {
+    
+        @Override
+        public int size() {
+            return 0;
+        }
+    
+        @Override
+        public boolean isEmpty() {
+            return false;
+        }
+    
+        @Override
+        public boolean containsKey(Object key) {
+            return false;
+        }
+    
+        @Override
+        public boolean containsValue(Object value) {
+            return false;
+        }
+    
+        @Override
+        public Object get(Object key) {
+            return null;
+        }
+    
+        @Override
+        public Object put(Object key, Object value) {
+            return null;
+        }
+    
+        @Override
+        public Object remove(Object key) {
+            return null;
+        }
+    
+        @Override
+        public void putAll(Map m) {
+        }
+    
+        @Override
+        public void clear() {
+        }
+    
+        @Override
+        public Set keySet() {
+            return null;
+        }
+    
+        @Override
+        public Collection values() {
+            return null;
+        }
+    
+        @Override
+        public Set entrySet() {
+            return null;
+        }
+        
+    }
+
+    private OverloadedMethodsSubset newOverloadedMethodsSubset(Class cl, String methodName, final boolean desc) {
+        final Method[] ms = cl.getMethods();
+        
+        final List<Method> filteredMethods = new ArrayList();
+        for (Method m : ms) {
+            if (m.getName().equals(methodName)) {
+                filteredMethods.add(m);
+            }
+        }
+        // As the order in which getMethods() returns the methods is undefined, we sort them for test predictability: 
+        Collections.sort(filteredMethods, new Comparator<Method>() {
+            @Override
+            public int compare(Method o1, Method o2) {
+                int res = o1.toString().compareTo(o2.toString());
+                return desc ? -res : res;
+            }
+        });
+        
+        final OverloadedMethodsSubset oms = cl.getName().indexOf("VarArgs") == -1
+                ? new OverloadedFixArgsMethods() : new OverloadedVarArgsMethods();
+        for (Method m : filteredMethods) {
+            oms.addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(m, m.getParameterTypes()));
+        }
+        return oms;
+    }
+    
+    private void checkTypeFlags(Class cl, String methodName, int... expectedTypeFlags) {
+        checkTypeFlags(cl, methodName, false, expectedTypeFlags);
+        checkTypeFlags(cl, methodName, true, expectedTypeFlags);
+    }
+    
+    private void checkTypeFlags(Class cl, String methodName, boolean revMetOrder, int... expectedTypeFlags) {
+        int[] actualParamTypes = getTypeFlags(cl, methodName, revMetOrder, expectedTypeFlags.length);
+        assertNotNull("Method " + methodName + "(#" + expectedTypeFlags.length + ") doesn't exist", actualParamTypes);
+        if (actualParamTypes != OverloadedMethodsSubset.ALL_ZEROS_ARRAY) {
+            assertEquals(expectedTypeFlags.length, actualParamTypes.length);
+            for (int i = 0; i < expectedTypeFlags.length; i++) {
+                assertEquals(expectedTypeFlags[i], actualParamTypes[i]);
+            }
+        } else {
+            for (int expectedTypeFlag : expectedTypeFlags) {
+                assertEquals(expectedTypeFlag, 0);
+            }
+        }
+    }
+
+    private int[] getTypeFlags(Class cl, String methodName, boolean revMetOrder, int paramCnt) {
+        OverloadedMethodsSubset oms = newOverloadedMethodsSubset(cl, methodName, revMetOrder);
+        int[] actualParamTypes = oms.getTypeFlags(paramCnt);
+        return actualParamTypes;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/AbstractParallelIntrospectionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/AbstractParallelIntrospectionTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/AbstractParallelIntrospectionTest.java
deleted file mode 100644
index 62c4251..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/AbstractParallelIntrospectionTest.java
+++ /dev/null
@@ -1,125 +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.beans;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateMethodModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-
-import junit.framework.TestCase;
-
-public abstract class AbstractParallelIntrospectionTest extends TestCase {
-    
-    private static final int NUM_THREADS = 8;
-    private static final int NUM_ENTITYES = 8;
-    private static final int NUM_MEMBERS = 8;
-    private static final int ITERATIONS = 20000;
-    private static final double CACHE_CLEARING_CHANCE = 0.01;
-    
-    private BeansWrapper beansWrapper = new BeansWrapper(Configuration.VERSION_3_0_0);
-    
-    public AbstractParallelIntrospectionTest(String name) {
-        super(name);
-    }
-    
-    public void testReliability() {
-        testReliability(ITERATIONS);
-    }
-    
-    public void testReliability(int iterations) {
-        TestThread[] ts = new TestThread[NUM_THREADS]; 
-        for (int i = 0; i < NUM_THREADS; i++) {
-            ts[i] = new TestThread(iterations);
-            ts[i].start();
-        }
-
-        for (int i = 0; i < NUM_THREADS; i++) {
-            try {
-                ts[i].join();
-                if (ts[i].error != null) {
-                    throw new AssertionError(ts[i].error);
-                }
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    protected abstract TemplateHashModel getWrappedEntity(int objIdx) throws TemplateModelException;
-    
-    protected final BeansWrapper getBeansWrapper() {
-        return beansWrapper;
-    }
-    
-    private class TestThread extends Thread {
-        
-        private final int iterations;
-        
-        private Throwable error;
-        
-        private TestThread(int iterations) {
-            this.iterations = iterations;
-        }
-
-        @Override
-        public void run() {
-            try {
-                for (int i = 0; i < iterations; i++) {
-                    if (Math.random() < CACHE_CLEARING_CHANCE) {
-                        beansWrapper.clearClassIntrospecitonCache();
-                    }
-                    int objIdx = (int) (Math.random() * NUM_ENTITYES);
-                    TemplateHashModel h = getWrappedEntity(objIdx);
-                    int mIdx = (int) (Math.random() * NUM_MEMBERS);
-                    testProperty(h, objIdx, mIdx);
-                    testMethod(h, objIdx, mIdx);
-                }
-            } catch (Throwable e) {
-                error = e;
-            }
-        }
-
-        private void testProperty(TemplateHashModel h, int objIdx, int mIdx)
-                throws TemplateModelException, AssertionError {
-            TemplateNumberModel pv = (TemplateNumberModel) h.get("p" + mIdx);
-            final int expected = objIdx * 1000 + mIdx;
-            final int got = pv.getAsNumber().intValue();
-            if (got != expected) {
-                throw new AssertionError("Property assertation failed; " +
-                        "expected " + expected + ", but got " + got);
-            }
-        }
-
-        private void testMethod(TemplateHashModel h, int objIdx, int mIdx)
-                throws TemplateModelException, AssertionError {
-            TemplateMethodModel pv = (TemplateMethodModel) h.get("m" + mIdx);
-            final int expected = objIdx * 1000 + mIdx;
-            final int got = ((TemplateNumberModel) pv.exec(null)).getAsNumber().intValue();
-            if (got != expected) {
-                throw new AssertionError("Method assertation failed; " +
-                        "expected " + expected + ", but got " + got);
-            }
-        }
-        
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/AlphabeticalMethodSorter.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/AlphabeticalMethodSorter.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/AlphabeticalMethodSorter.java
deleted file mode 100644
index 57611f1..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/AlphabeticalMethodSorter.java
+++ /dev/null
@@ -1,49 +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.beans;
-
-import java.beans.MethodDescriptor;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-
-class AlphabeticalMethodSorter implements MethodSorter {
-
-    private final boolean desc;
-    
-    public AlphabeticalMethodSorter(boolean desc) {
-        this.desc = desc;
-    }
-
-    @Override
-    public MethodDescriptor[] sortMethodDescriptors(MethodDescriptor[] methodDescriptors) {
-        ArrayList<MethodDescriptor> ls = new ArrayList<>(Arrays.asList(methodDescriptors));
-        Collections.sort(ls, new Comparator<MethodDescriptor>() {
-            @Override
-            public int compare(MethodDescriptor o1, MethodDescriptor o2) {
-                int res = o1.getMethod().toString().compareTo(o2.getMethod().toString());
-                return desc ? -res : res;
-            }
-        });
-        return ls.toArray(new MethodDescriptor[ls.size()]);
-    }
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansAPINewInstanceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansAPINewInstanceTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansAPINewInstanceTest.java
deleted file mode 100644
index 34e2a4f..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansAPINewInstanceTest.java
+++ /dev/null
@@ -1,134 +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.beans;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.test.util.TestUtil;
-
-import junit.framework.TestCase;
-
-public class BeansAPINewInstanceTest extends TestCase {
-
-    private BeansWrapper beansWrapper = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-
-    public BeansAPINewInstanceTest(String name) {
-        super(name);
-    }
-    
-    @SuppressWarnings("boxing")
-    public void test() throws Exception {
-        testClassConstructors("");
-        testClassConstructors("int... [1]", 1);
-        testClassConstructors("int 1, int 2", 1, 2);
-        testClassConstructors("int... [1, 2, 3]", 1, 2, 3);
-        testClassConstructors("int... [1, 2, 3]", 1, (byte) 2, (short) 3);
-        try {
-            testClassConstructors("int... [1, 2, 3]", 1, 2, (long) 3);
-            fail();
-        } catch (NoSuchMethodException e) {
-            // Expected
-        }
-        
-        testClassConstructors("int 1, int 2", (byte) 1, (short) 2);
-        testClassConstructors("double 1.0, double 2.0", (long) 1, (short) 2);
-        testClassConstructors("double 1.0, double 2.0", 1, 2f);
-        testClassConstructors("Integer null, Integer null", null, null);
-        testClassConstructors("Integer null, Integer 1", null, 1);
-        testClassConstructors("int 1, String s", 1, "s");
-        testClassConstructors("int 1, String null", 1, null);
-        testClassConstructors("Object null, Object s", null, "s");
-        testClassConstructors("Object 1.0, Object s", 1f, "s");
-        
-        testClassConstructors("Object s, int... [1, 2]", "s", 1, 2);
-        testClassConstructors("Object s, int... []", "s");
-        
-        testClassConstructors2("int 1, int 2", (byte) 1, (short) 2);
-        try {
-            testClassConstructors2("int 1, int 2", 1, 2L);
-            fail();
-        } catch (NoSuchMethodException e) {
-            // Expected
-        }
-        try {
-            testClassConstructors2("", "", "");
-            fail();
-        } catch (NoSuchMethodException e) {
-            // Expected
-        }
-        try {
-            testClassConstructors2("int 1", 1);
-            fail();
-        } catch (NoSuchMethodException e) {
-            // Expected
-        }
-        try {
-            testClassConstructors2("");
-            fail();
-        } catch (NoSuchMethodException e) {
-            // Expected
-        }
-    }
-    
-    private void testClassConstructors(String expected, Object... args) throws Exception {
-        testCall(expected, Constructors.class, args); 
-    }
-
-    private void testClassConstructors2(String expected, Object... args) throws Exception {
-        testCall(expected, Constructors2.class, args); 
-    }
-    
-    private void testCall(String expected, Class cl, Object... args) throws Exception {
-        Object obj = _BeansAPI.newInstance(cl, args, beansWrapper); 
-        assertEquals(expected, obj.toString());        
-    }
-    
-    public static class Constructors {
-        private final String s;
-
-        public Constructors() { s = ""; }
-        
-        public Constructors(int x, int y) { s = "int " + x + ", int " + y; }
-        public Constructors(int x, String y) { s = "int " + x + ", String " + y; }
-        public Constructors(int x, long y) { s = "int " + x + ", long " + y; }
-        public Constructors(double x, double y) { s = "double " + x + ", double " + y; }
-        public Constructors(Integer x, Integer y) { s = "Integer " + x + ", Integer " + y; }
-        public Constructors(Object x, Object y) { s = "Object " + x + ", Object " + y; }
-
-        public Constructors(int... xs) { s = "int... " + TestUtil.arrayToString(xs); }
-        public Constructors(Object x, int... ys) { s = "Object " + x + ", int... " + TestUtil.arrayToString(ys); }
-        
-        @Override
-        public String toString() {
-            return s;
-        }
-    }
-
-    public static class Constructors2 {
-        private final String s;
-
-        public Constructors2(int x, int y) { s = "int " + x + ", int " + y; }
-        
-        @Override
-        public String toString() {
-            return s;
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBasics.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBasics.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBasics.java
deleted file mode 100644
index bc6b8d7..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBasics.java
+++ /dev/null
@@ -1,62 +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.beans;
-
-import static org.junit.Assert.*;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
-import org.junit.Test;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
-public class BeansWrapperBasics {
-
-    @SuppressWarnings("boxing")
-    @Test
-    public void testAPIBuiltInSupport() throws TemplateModelException {
-        {
-            BeansWrapper bw = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-            assertWrappingResult(StringModel.class, bw.wrap("s"));
-            assertWrappingResult(NumberModel.class, bw.wrap(1.5));
-            assertWrappingResult(BooleanModel.class, bw.wrap(true));
-            assertWrappingResult(CollectionModel.class, bw.wrap(ImmutableList.of(1)));
-            assertWrappingResult(MapModel.class, bw.wrap(ImmutableMap.of("a", 1)));
-        }
-        
-        {
-            BeansWrapperBuilder bwb = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            bwb.setSimpleMapWrapper(true);
-            BeansWrapper bw = bwb.build();
-            assertWrappingResult(SimpleMapModel.class, bw.wrap(ImmutableMap.of("a", 1)));
-        }
-    }
-
-    private void assertWrappingResult(Class<? extends TemplateModel> expectedClass, TemplateModel tm)
-            throws TemplateModelException {
-        assertTrue(expectedClass.isInstance(tm));
-        // All BeansWrapper products support `?api`:
-        assertTrue(((TemplateModelWithAPISupport) tm).getAPI() instanceof APIModel);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperCachesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperCachesTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperCachesTest.java
deleted file mode 100644
index 8221a36..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperCachesTest.java
+++ /dev/null
@@ -1,99 +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.beans;
-
-import static org.junit.Assert.*;
-
-import java.lang.ref.Reference;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecision;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecisionInput;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class BeansWrapperCachesTest {
-
-    @Test
-    public void introspectionSettingChanges() {
-        BeansWrapper bw = new BeansWrapper(Configuration.VERSION_3_0_0);
-        ClassIntrospector ci1 = bw.getClassIntrospector();
-        checkRegisteredModelFactories(ci1, bw.getStaticModels(), bw.getEnumModels(), bw.getModelCache());
-        
-        bw.setExposeFields(true);
-        ClassIntrospector ci2 = bw.getClassIntrospector();
-        assertNotSame(ci1, ci2);
-        checkRegisteredModelFactories(ci1);
-        checkRegisteredModelFactories(ci2, bw.getStaticModels(), bw.getEnumModels(), bw.getModelCache());
-        
-        bw.setExposureLevel(BeansWrapper.EXPOSE_ALL);
-        ClassIntrospector ci3 = bw.getClassIntrospector();
-        assertNotSame(ci2, ci3);
-        checkRegisteredModelFactories(ci2);
-        checkRegisteredModelFactories(ci3, bw.getStaticModels(), bw.getEnumModels(), bw.getModelCache());
-        
-        MethodAppearanceFineTuner maf = new MethodAppearanceFineTuner() {
-            @Override
-            public void process(MethodAppearanceDecisionInput in, MethodAppearanceDecision out) {
-                // nop
-            }
-        };
-        bw.setMethodAppearanceFineTuner(maf);
-        ClassIntrospector ci4 = bw.getClassIntrospector();
-        assertNotSame(ci3, ci4);
-        checkRegisteredModelFactories(ci3);
-        checkRegisteredModelFactories(ci4, bw.getStaticModels(), bw.getEnumModels(), bw.getModelCache());
-        
-        bw.setExposeFields(true);
-        assertSame(ci4, bw.getClassIntrospector());
-        bw.setExposureLevel(BeansWrapper.EXPOSE_ALL);
-        assertSame(ci4, bw.getClassIntrospector());
-        bw.setMethodAppearanceFineTuner(maf);
-        assertSame(ci4, bw.getClassIntrospector());
-        checkRegisteredModelFactories(ci4, bw.getStaticModels(), bw.getEnumModels(), bw.getModelCache());
-    }
-    
-    private void checkRegisteredModelFactories(ClassIntrospector ci, Object... expected) {
-        Object[] actualRefs = ci.getRegisteredModelFactoriesSnapshot();
-
-        scanActuals: for (Object actaulRef : actualRefs) {
-            Object actualItem = ((Reference) actaulRef).get();
-            for (Object expectedItem : expected) {
-                if (actualItem == expectedItem) {
-                    continue scanActuals;
-                }
-            }
-            fail("Actaul item " + actualItem + " is not among the expected items");
-        }
-        
-        scanExpecteds: for (Object expectedItem : expected) {
-            for (Object ref : actualRefs) {
-                Object actualItem = ((Reference) ref).get();
-                if (actualItem == expectedItem) {
-                    continue scanExpecteds;
-                }
-            }
-            fail("Expected item " + expectedItem + " is not among the actual items");
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperDesc.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperDesc.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperDesc.java
deleted file mode 100644
index 6fd9a8c..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperDesc.java
+++ /dev/null
@@ -1,30 +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.beans;
-
-import org.apache.freemarker.core.Configuration;
-
-public class BeansWrapperDesc extends BeansWrapperWithShortedMethods {
-
-    public BeansWrapperDesc() {
-        super(Configuration.VERSION_3_0_0, true);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperInc.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperInc.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperInc.java
deleted file mode 100644
index b347b95..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperInc.java
+++ /dev/null
@@ -1,30 +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.beans;
-
-import org.apache.freemarker.core.Configuration;
-
-public class BeansWrapperInc extends BeansWrapperWithShortedMethods {
-
-    public BeansWrapperInc() {
-        super(Configuration.VERSION_3_0_0, false);
-    }
-
-}



[19/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..9c483d6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
@@ -0,0 +1,362 @@
+/*
+ * 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.IndexedPropertyDescriptor;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core._DelayedFTLTypeDescription;
+import org.apache.freemarker.core._DelayedJQuote;
+import org.apache.freemarker.core._TemplateModelException;
+import org.apache.freemarker.core.model.AdapterTemplateModel;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+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.TemplateModelIterator;
+import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.WrapperTemplateModel;
+import org.apache.freemarker.core.util._StringUtil;
+import org.slf4j.Logger;
+
+/**
+ * A class that will wrap an arbitrary object into {@link org.apache.freemarker.core.model.TemplateHashModel}
+ * interface allowing calls to arbitrary property getters and invocation of
+ * accessible methods on the object from a template using the
+ * <tt>object.foo</tt> to access properties and <tt>object.bar(arg1, arg2)</tt> to
+ * invoke methods on it. You can also use the <tt>object.foo[index]</tt> syntax to
+ * access indexed properties. It uses Beans {@link java.beans.Introspector}
+ * to dynamically discover the properties and methods. 
+ */
+
+public class BeanModel
+implements
+    TemplateHashModelEx, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport {
+    
+    private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
+    
+    protected final Object object;
+    protected final DefaultObjectWrapper wrapper;
+    
+    // We use this to represent an unknown value as opposed to known value of null (JR)
+    static final TemplateModel UNKNOWN = new SimpleScalar("UNKNOWN");
+    
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new BeanModel(object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+
+    // I've tried to use a volatile ConcurrentHashMap field instead of HashMap + synchronized(this), but oddly it was
+    // a bit slower, at least on Java 8 u66. 
+    private HashMap<Object, TemplateModel> memberCache;
+
+    /**
+     * Creates a new model that wraps the specified object. Note that there are
+     * specialized subclasses of this class for wrapping arrays, collections,
+     * enumeration, iterators, and maps. Note also that the superclass can be
+     * used to wrap String objects if only scalar functionality is needed. You
+     * can also choose to delegate the choice over which model class is used for
+     * wrapping to {@link DefaultObjectWrapper#wrap(Object)}.
+     * @param object the object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     */
+    public BeanModel(Object object, DefaultObjectWrapper wrapper) {
+        // [2.4]: All models were introspected here, then the results was discareded, and get() will just do the
+        // introspection again. So is this necessary? (The inrospectNow parameter was added in 2.3.21 to allow
+        // lazy-introspecting DefaultObjectWrapper.trueModel|falseModel.)
+        this(object, wrapper, true);
+    }
+
+    /** @since 2.3.21 */
+    BeanModel(Object object, DefaultObjectWrapper wrapper, boolean inrospectNow) {
+        this.object = object;
+        this.wrapper = wrapper;
+        if (inrospectNow && object != null) {
+            // [2.4]: Could this be removed?
+            wrapper.getClassIntrospector().get(object.getClass());
+        }
+    }
+    
+    /**
+     * Uses Beans introspection to locate a property or method with name
+     * matching the key name. If a method or property is found, it's wrapped
+     * into {@link org.apache.freemarker.core.model.TemplateMethodModelEx} (for a method or
+     * indexed property), or evaluated on-the-fly and the return value wrapped
+     * into appropriate model (for a simple property) Models for various
+     * properties and methods are cached on a per-class basis, so the costly
+     * introspection is performed only once per property or method of a class.
+     * (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-
+     * 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
+     * environment that does this kind of things--servlet containers do it when
+     * they reload a web application) and flushes the cache. If no method or
+     * property matching the key is found, the framework will try to invoke
+     * methods with signature
+     * <tt>non-void-return-type get(java.lang.String)</tt>,
+     * then <tt>non-void-return-type get(java.lang.Object)</tt>, or 
+     * alternatively (if the wrapped object is a resource bundle) 
+     * <tt>Object getObject(java.lang.String)</tt>.
+     * @throws TemplateModelException if there was no property nor method nor
+     * a generic <tt>get</tt> method to invoke.
+     */
+    @Override
+    public TemplateModel get(String key)
+        throws TemplateModelException {
+        Class<?> clazz = object.getClass();
+        Map<Object, Object> classInfo = wrapper.getClassIntrospector().get(clazz);
+        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);
+                }
+            } 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;
+                    }
+                }
+            }
+            if (retval == UNKNOWN) {
+                if (wrapper.isStrict()) {
+                    throw new InvalidPropertyException("No such bean property: " + key);
+                } else {
+                    logNoSuchKey(key, classInfo);
+                }
+                retval = wrapper.wrap(null);
+            }
+            return retval;
+        } catch (TemplateModelException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new _TemplateModelException(e,
+                    "An error has occurred when reading existing sub-variable ", new _DelayedJQuote(key),
+                    "; see cause exception! The type of the containing value was: ",
+                    new _DelayedFTLTypeDescription(this)
+            );
+        }
+    }
+
+    private void logNoSuchKey(String key, Map<?, ?> keyMap) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Key " + _StringUtil.jQuoteNoXSS(key) + " was not found on instance of " + 
+                object.getClass().getName() + ". Introspection information for " +
+                "the class is: " + keyMap);
+        }
+    }
+    
+    /**
+     * Whether the model has a plain get(String) or get(Object) method
+     */
+    
+    protected boolean hasPlainGetMethod() {
+        return wrapper.getClassIntrospector().get(object.getClass()).get(ClassIntrospector.GENERIC_GET_KEY) != null;
+    }
+    
+    private TemplateModel invokeThroughDescriptor(Object desc, Map<Object, Object> classInfo)
+            throws IllegalAccessException, InvocationTargetException, TemplateModelException {
+        // See if this particular instance has a cached implementation for the requested feature descriptor
+        TemplateModel cachedModel;
+        synchronized (this) {
+            cachedModel = memberCache != null ? memberCache.get(desc) : null;
+        }
+
+        if (cachedModel != null) {
+            return cachedModel;
+        }
+
+        TemplateModel resultModel = UNKNOWN;
+        if (desc instanceof IndexedPropertyDescriptor) {
+            Method readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod(); 
+            resultModel = cachedModel = 
+                new SimpleMethodModel(object, readMethod, 
+                        ClassIntrospector.getArgTypes(classInfo, readMethod), wrapper);
+        } else if (desc instanceof PropertyDescriptor) {
+            PropertyDescriptor pd = (PropertyDescriptor) desc;
+            resultModel = wrapper.invokeMethod(object, pd.getReadMethod(), null);
+            // cachedModel remains null, as we don't cache these
+        } else if (desc instanceof Field) {
+            resultModel = wrapper.wrap(((Field) desc).get(object));
+            // cachedModel remains null, as we don't cache these
+        } else if (desc instanceof Method) {
+            Method method = (Method) desc;
+            resultModel = cachedModel = new SimpleMethodModel(
+                    object, method, ClassIntrospector.getArgTypes(classInfo, method), wrapper);
+        } else if (desc instanceof OverloadedMethods) {
+            resultModel = cachedModel = new OverloadedMethodsModel(
+                    object, (OverloadedMethods) desc, wrapper);
+        }
+        
+        // If new cachedModel was created, cache it
+        if (cachedModel != null) {
+            synchronized (this) {
+                if (memberCache == null) {
+                    memberCache = new HashMap<>();
+                }
+                memberCache.put(desc, cachedModel);
+            }
+        }
+        return resultModel;
+    }
+    
+    void clearMemberCache() {
+        synchronized (this) {
+            memberCache = null;
+        }
+    }
+
+    protected TemplateModel invokeGenericGet(Map/*<Object, Object>*/ classInfo, Class<?> clazz, String key)
+            throws IllegalAccessException, InvocationTargetException,
+        TemplateModelException {
+        Method genericGet = (Method) classInfo.get(ClassIntrospector.GENERIC_GET_KEY);
+        if (genericGet == null) {
+            return UNKNOWN;
+        }
+
+        return wrapper.invokeMethod(object, genericGet, new Object[] { key });
+    }
+
+    protected TemplateModel wrap(Object obj)
+    throws TemplateModelException {
+        return wrapper.getOuterIdentity().wrap(obj);
+    }
+    
+    protected Object unwrap(TemplateModel model)
+    throws TemplateModelException {
+        return wrapper.unwrap(model);
+    }
+
+    /**
+     * Tells whether the model is considered to be empty.
+     * It is empty if the wrapped object is a 0 length {@link String}, or an empty {@link Collection} or and empty
+     * {@link Map}, or an {@link Iterator} that has no more items, or a {@link Boolean#FALSE}, or {@code null}. 
+     */
+    @Override
+    public boolean isEmpty() {
+        if (object instanceof String) {
+            return ((String) object).length() == 0;
+        }
+        if (object instanceof Collection) {
+            return ((Collection<?>) object).isEmpty();
+        }
+        if (object instanceof Iterator) {
+            return !((Iterator<?>) object).hasNext();
+        }
+        if (object instanceof Map) {
+            return ((Map<?,?>) object).isEmpty();
+        }
+        // [FM3] Why's FALSE empty? 
+        return object == null || Boolean.FALSE.equals(object);
+    }
+    
+    /**
+     * Returns the same as {@link #getWrappedObject()}; to ensure that, this method will be final starting from 2.4.
+     * This behavior of {@link BeanModel} is assumed by some FreeMarker code. 
+     */
+    @Override
+    public Object getAdaptedObject(Class<?> hint) {
+        return object;  // return getWrappedObject(); starting from 2.4
+    }
+
+    @Override
+    public Object getWrappedObject() {
+        return object;
+    }
+    
+    @Override
+    public int size() {
+        return wrapper.getClassIntrospector().keyCount(object.getClass());
+    }
+
+    @Override
+    public TemplateCollectionModel keys() {
+        return new CollectionAndSequence(new SimpleSequence(keySet(), wrapper));
+    }
+
+    @Override
+    public TemplateCollectionModel values() throws TemplateModelException {
+        List<Object> values = new ArrayList<>(size());
+        TemplateModelIterator it = keys().iterator();
+        while (it.hasNext()) {
+            String key = ((TemplateScalarModel) it.next()).getAsString();
+            values.add(get(key));
+        }
+        return new CollectionAndSequence(new SimpleSequence(values, wrapper));
+    }
+    
+    @Override
+    public String toString() {
+        return object.toString();
+    }
+
+    /**
+     * Helper method to support TemplateHashModelEx. Returns the Set of
+     * Strings which are available via the TemplateHashModel
+     * interface. Subclasses that override <tt>invokeGenericGet</tt> to
+     * provide additional hash keys should also override this method.
+     */
+    protected Set/*<Object>*/ keySet() {
+        return wrapper.getClassIntrospector().keySet(object.getClass());
+    }
+
+    @Override
+    public TemplateModel getAPI() throws TemplateModelException {
+        return wrapper.wrapAsAPI(object);
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/BeansModelCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/BeansModelCache.java b/src/main/java/org/apache/freemarker/core/model/impl/BeansModelCache.java
new file mode 100644
index 0000000..7808d48
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/BeansModelCache.java
@@ -0,0 +1,73 @@
+/*
+ * 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.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.freemarker.core.model.TemplateModel;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+public class BeansModelCache extends ModelCache {
+    private final Map classToFactory = new ConcurrentHashMap();
+    private final Set mappedClassNames = new HashSet();
+
+    private final DefaultObjectWrapper wrapper;
+    
+    BeansModelCache(DefaultObjectWrapper wrapper) {
+        this.wrapper = wrapper;
+    }
+    
+    @Override
+    protected boolean isCacheable(Object object) {
+        return object.getClass() != Boolean.class; 
+    }
+    
+    @Override
+    @SuppressFBWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER", justification="Locks for factory creation only")
+    protected TemplateModel create(Object object) {
+        Class clazz = object.getClass();
+        
+        ModelFactory factory = (ModelFactory) classToFactory.get(clazz);
+        
+        if (factory == null) {
+            // Synchronized so that we won't unnecessarily create the same factory for multiple times in parallel.
+            synchronized (classToFactory) {
+                factory = (ModelFactory) classToFactory.get(clazz);
+                if (factory == null) {
+                    String className = clazz.getName();
+                    // clear mappings when class reloading is detected
+                    if (!mappedClassNames.add(className)) {
+                        classToFactory.clear();
+                        mappedClassNames.clear();
+                        mappedClassNames.add(className);
+                    }
+                    factory = wrapper.getModelFactory(clazz);
+                    classToFactory.put(clazz, factory);
+                }
+            }
+        }
+        
+        return factory.create(object, wrapper);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/BooleanModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/BooleanModel.java b/src/main/java/org/apache/freemarker/core/model/impl/BooleanModel.java
new file mode 100644
index 0000000..9db421f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/BooleanModel.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+
+/**
+ * <p>A class that will wrap instances of {@link java.lang.Boolean} into a
+ * {@link TemplateBooleanModel}.
+ */
+public class BooleanModel extends BeanModel implements TemplateBooleanModel {
+    private final boolean value;
+    
+    public BooleanModel(Boolean bool, DefaultObjectWrapper wrapper) {
+        super(bool, wrapper, false);
+        value = bool.booleanValue();
+    }
+
+    @Override
+    public boolean getAsBoolean() {
+        return value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/CallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/CallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/CallableMemberDescriptor.java
new file mode 100644
index 0000000..5548796
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/CallableMemberDescriptor.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Packs a {@link Method} or {@link Constructor} together with its parameter types. The actual
+ * {@link Method} or {@link Constructor} is not exposed by the API, because in rare cases calling them require
+ * type conversion that the Java reflection API can't do, hence the developer shouldn't be tempted to call them
+ * directly. 
+ */
+abstract class CallableMemberDescriptor extends MaybeEmptyCallableMemberDescriptor {
+
+    abstract TemplateModel invokeMethod(DefaultObjectWrapper ow, Object obj, Object[] args)
+            throws TemplateModelException, InvocationTargetException, IllegalAccessException;
+
+    abstract Object invokeConstructor(DefaultObjectWrapper ow, Object[] args)
+            throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
+            TemplateModelException;
+    
+    abstract String getDeclaration();
+    
+    abstract boolean isConstructor();
+    
+    abstract boolean isStatic();
+
+    abstract boolean isVarargs();
+
+    abstract Class[] getParamTypes();
+
+    abstract String getName();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/CharacterOrString.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/CharacterOrString.java b/src/main/java/org/apache/freemarker/core/model/impl/CharacterOrString.java
new file mode 100644
index 0000000..0e31d80
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/CharacterOrString.java
@@ -0,0 +1,45 @@
+/*
+ * 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.model.TemplateScalarModel;
+
+/**
+ * Represents value unwrapped both to {@link Character} and {@link String}. This is needed for unwrapped overloaded
+ * method parameters where both {@link Character} and {@link String} occurs on the same parameter position when the
+ * {@link TemplateScalarModel} to unwrapp contains a {@link String} of length 1.
+ */
+final class CharacterOrString {
+
+    private final String stringValue;
+
+    CharacterOrString(String stringValue) {
+        this.stringValue = stringValue;
+    }
+    
+    String getAsString() {
+        return stringValue;
+    }
+
+    char getAsChar() {
+        return stringValue.charAt(0);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java b/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
new file mode 100644
index 0000000..8b72f2b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
@@ -0,0 +1,148 @@
+/*
+ * 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.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+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.util._ClassUtil;
+
+/**
+ * Base class for hash models keyed by Java class names. 
+ */
+abstract class ClassBasedModelFactory implements TemplateHashModel {
+    private final DefaultObjectWrapper wrapper;
+    
+    private final Map/*<String,TemplateModel>*/ cache = new ConcurrentHashMap();
+    private final Set classIntrospectionsInProgress = new HashSet();
+    
+    protected ClassBasedModelFactory(DefaultObjectWrapper wrapper) {
+        this.wrapper = wrapper;
+    }
+
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        try {
+            return getInternal(key);
+        } catch (Exception e) {
+            if (e instanceof TemplateModelException) {
+                throw (TemplateModelException) e;
+            } else {
+                throw new TemplateModelException(e);
+            }
+        }
+    }
+
+    private TemplateModel getInternal(String key) throws TemplateModelException, ClassNotFoundException {
+        {
+            TemplateModel model = (TemplateModel) cache.get(key);
+            if (model != null) return model;
+        }
+
+        final ClassIntrospector classIntrospector;
+        int classIntrospectorClearingCounter;
+        final Object sharedLock = wrapper.getSharedIntrospectionLock();
+        synchronized (sharedLock) {
+            TemplateModel model = (TemplateModel) cache.get(key);
+            if (model != null) return model;
+            
+            while (model == null
+                    && classIntrospectionsInProgress.contains(key)) {
+                // Another thread is already introspecting this class;
+                // waiting for its result.
+                try {
+                    sharedLock.wait();
+                    model = (TemplateModel) cache.get(key);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(
+                            "Class inrospection data lookup aborded: " + e);
+                }
+            }
+            if (model != null) return model;
+            
+            // This will be the thread that introspects this class.
+            classIntrospectionsInProgress.add(key);
+
+            // While the classIntrospector should not be changed from another thread, badly written apps can do that,
+            // and it's cheap to get the classIntrospector from inside the lock here:   
+            classIntrospector = wrapper.getClassIntrospector();
+            classIntrospectorClearingCounter = classIntrospector.getClearingCounter();
+        }
+        try {
+            final Class clazz = _ClassUtil.forName(key);
+            
+            // This is called so that we trigger the
+            // class-reloading detector. If clazz is a reloaded class,
+            // the wrapper will in turn call our clearCache method.
+            // TODO: Why do we check it now and only now?
+            classIntrospector.get(clazz);
+            
+            TemplateModel model = createModel(clazz);
+            // Warning: model will be null if the class is not good for the subclass.
+            // For example, EnumModels#createModel returns null if clazz is not an enum.
+            
+            if (model != null) {
+                synchronized (sharedLock) {
+                    // Save it into the cache, but only if nothing relevant has changed while we were outside the lock: 
+                    if (classIntrospector == wrapper.getClassIntrospector()
+                            && classIntrospectorClearingCounter == classIntrospector.getClearingCounter()) {  
+                        cache.put(key, model);
+                    }
+                }
+            }
+            return model;
+        } finally {
+            synchronized (sharedLock) {
+                classIntrospectionsInProgress.remove(key);
+                sharedLock.notifyAll();
+            }
+        }
+    }
+    
+    void clearCache() {
+        synchronized (wrapper.getSharedIntrospectionLock()) {
+            cache.clear();
+        }
+    }
+    
+    void removeFromCache(Class clazz) {
+        synchronized (wrapper.getSharedIntrospectionLock()) {
+            cache.remove(clazz.getName());
+        }
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+    
+    protected abstract TemplateModel createModel(Class clazz) 
+    throws TemplateModelException;
+    
+    protected DefaultObjectWrapper getWrapper() {
+        return wrapper;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ClassChangeNotifier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ClassChangeNotifier.java b/src/main/java/org/apache/freemarker/core/model/impl/ClassChangeNotifier.java
new file mode 100644
index 0000000..ddca496
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ClassChangeNotifier.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Reports when the non-private interface of a class was changed to the subscribers.   
+ */
+interface ClassChangeNotifier {
+    
+    /**
+     * @param classIntrospector Should only be weak-referenced from the monitor object.
+     */
+    void subscribe(ClassIntrospector classIntrospector);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..4dd8931
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -0,0 +1,807 @@
+/*
+ * 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.IndexedPropertyDescriptor;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyDescriptor;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.DecisionInput;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._NullArgumentException;
+import org.slf4j.Logger;
+
+/**
+ * Returns information about a {@link Class} that's useful for FreeMarker. Encapsulates a cache for this. Thread-safe,
+ * doesn't even require "proper publishing" starting from 2.3.24 or Java 5. Immutable, with the exception of the
+ * internal caches.
+ * 
+ * <p>
+ * Note that instances of this are cached on the level of FreeMarker's defining class loader. Hence, it must not do
+ * operations that depend on the Thread Context Class Loader, such as resolving class names.
+ */
+class ClassIntrospector {
+
+    // Attention: This class must be thread-safe (not just after proper publishing). This is important as some of
+    // these are shared by many object wrappers, and concurrency related glitches due to user errors must remain
+    // local to the object wrappers, not corrupting the shared ClassIntrospector.
+
+    private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
+
+    private static final String JREBEL_SDK_CLASS_NAME = "org.zeroturnaround.javarebel.ClassEventListener";
+    private static final String JREBEL_INTEGRATION_ERROR_MSG
+            = "Error initializing JRebel integration. JRebel integration disabled.";
+
+    private static final ClassChangeNotifier CLASS_CHANGE_NOTIFIER;
+    static {
+        boolean jRebelAvailable;
+        try {
+            Class.forName(JREBEL_SDK_CLASS_NAME);
+            jRebelAvailable = true;
+        } catch (Throwable e) {
+            jRebelAvailable = false;
+            try {
+                if (!(e instanceof ClassNotFoundException)) {
+                    LOG.error(JREBEL_INTEGRATION_ERROR_MSG, e);
+                }
+            } catch (Throwable loggingE) {
+                // ignore
+            }
+        }
+
+        ClassChangeNotifier classChangeNotifier;
+        if (jRebelAvailable) {
+            try {
+                classChangeNotifier = (ClassChangeNotifier)
+                        Class.forName("org.apache.freemarker.core.model.impl.JRebelClassChangeNotifier").newInstance();
+            } catch (Throwable e) {
+                classChangeNotifier = null;
+                try {
+                    LOG.error(JREBEL_INTEGRATION_ERROR_MSG, e);
+                } catch (Throwable loggingE) {
+                    // ignore
+                }
+            }
+        } else {
+            classChangeNotifier = null;
+        }
+
+        CLASS_CHANGE_NOTIFIER = classChangeNotifier;
+    }
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Introspection info Map keys:
+
+    /** Key in the class info Map to the Map that maps method to argument type arrays */
+    private static final Object ARG_TYPES_BY_METHOD_KEY = new Object();
+    /** Key in the class info Map to the object that represents the constructors (one or multiple due to overloading) */
+    static final Object CONSTRUCTORS_KEY = new Object();
+    /** Key in the class info Map to the get(String|Object) Method */
+    static final Object GENERIC_GET_KEY = new Object();
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Introspection configuration properties:
+
+    // Note: These all must be *declared* final (or else synchronization is needed everywhere where they are accessed).
+
+    final int exposureLevel;
+    final boolean exposeFields;
+    final MethodAppearanceFineTuner methodAppearanceFineTuner;
+    final MethodSorter methodSorter;
+
+    /** See {@link #getHasSharedInstanceRestrictons()} */
+    final private boolean hasSharedInstanceRestrictons;
+
+    /** See {@link #isShared()} */
+    final private boolean shared;
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // State fields:
+
+    private final Object sharedLock;
+    private final Map<Class<?>, Map<Object, Object>> cache
+            = new ConcurrentHashMap<>(0, 0.75f, 16);
+    private final Set<String> cacheClassNames = new HashSet<>(0);
+    private final Set<Class<?>> classIntrospectionsInProgress = new HashSet<>(0);
+
+    private final List<WeakReference<Object/*ClassBasedModelFactory|ModelCache>*/>> modelFactories
+            = new LinkedList<>();
+    private final ReferenceQueue<Object> modelFactoriesRefQueue = new ReferenceQueue<>();
+
+    private int clearingCounter;
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Instantiation:
+
+    /**
+     * Creates a new instance, that is hence surely not shared (singleton) instance.
+     * 
+     * @param pa
+     *            Stores what the values of the JavaBean properties of the returned instance will be. Not {@code null}.
+     */
+    ClassIntrospector(ClassIntrospectorBuilder pa, Object sharedLock) {
+        this(pa, sharedLock, false, false);
+    }
+
+    /**
+     * @param hasSharedInstanceRestrictons
+     *            {@code true} exactly if we are creating a new instance with {@link ClassIntrospectorBuilder}. 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) {
+        _NullArgumentException.check("sharedLock", sharedLock);
+
+        exposureLevel = builder.getExposureLevel();
+        exposeFields = builder.getExposeFields();
+        methodAppearanceFineTuner = builder.getMethodAppearanceFineTuner();
+        methodSorter = builder.getMethodSorter();
+
+        this.sharedLock = sharedLock;
+
+        this.hasSharedInstanceRestrictons = hasSharedInstanceRestrictons;
+        this.shared = shared;
+
+        if (CLASS_CHANGE_NOTIFIER != null) {
+            CLASS_CHANGE_NOTIFIER.subscribe(this);
+        }
+    }
+
+    /**
+     * 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.
+     */
+    ClassIntrospectorBuilder getPropertyAssignments() {
+        return new ClassIntrospectorBuilder(this);
+    }
+
+    // ------------------------------------------------------------------------------------------------------------------
+    // Introspection:
+
+    /**
+     * Gets the class introspection data from {@link #cache}, automatically creating the cache entry if it's missing.
+     * 
+     * @return A {@link Map} where each key is a property/method/field name (or a special {@link Object} key like
+     *         {@link #CONSTRUCTORS_KEY}), each value is a {@link PropertyDescriptor} or {@link Method} or
+     *         {@link OverloadedMethods} or {@link Field} (but better check the source code...).
+     */
+    Map<Object, Object> get(Class<?> clazz) {
+        {
+            Map<Object, Object> introspData = cache.get(clazz);
+            if (introspData != null) return introspData;
+        }
+
+        String className;
+        synchronized (sharedLock) {
+            Map<Object, Object> introspData = cache.get(clazz);
+            if (introspData != null) return introspData;
+
+            className = clazz.getName();
+            if (cacheClassNames.contains(className)) {
+                onSameNameClassesDetected(className);
+            }
+
+            while (introspData == null && classIntrospectionsInProgress.contains(clazz)) {
+                // Another thread is already introspecting this class;
+                // waiting for its result.
+                try {
+                    sharedLock.wait();
+                    introspData = cache.get(clazz);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(
+                            "Class inrospection data lookup aborded: " + e);
+                }
+            }
+            if (introspData != null) return introspData;
+
+            // This will be the thread that introspects this class.
+            classIntrospectionsInProgress.add(clazz);
+        }
+        try {
+            Map<Object, Object> introspData = createClassIntrospectionData(clazz);
+            synchronized (sharedLock) {
+                cache.put(clazz, introspData);
+                cacheClassNames.add(className);
+            }
+            return introspData;
+        } finally {
+            synchronized (sharedLock) {
+                classIntrospectionsInProgress.remove(clazz);
+                sharedLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Creates a {@link Map} with the content as described for the return value of {@link #get(Class)}.
+     */
+    private Map<Object, Object> createClassIntrospectionData(Class<?> clazz) {
+        final Map<Object, Object> introspData = new HashMap<>();
+
+        if (exposeFields) {
+            addFieldsToClassIntrospectionData(introspData, clazz);
+        }
+
+        final Map<MethodSignature, List<Method>> accessibleMethods = discoverAccessibleMethods(clazz);
+
+        addGenericGetToClassIntrospectionData(introspData, accessibleMethods);
+
+        if (exposureLevel != DefaultObjectWrapper.EXPOSE_NOTHING) {
+            try {
+                addBeanInfoToClassIntrospectionData(introspData, clazz, accessibleMethods);
+            } catch (IntrospectionException e) {
+                LOG.warn("Couldn't properly perform introspection for class {}", clazz.getName(), e);
+                introspData.clear(); // FIXME NBC: Don't drop everything here.
+            }
+        }
+
+        addConstructorsToClassIntrospectionData(introspData, clazz);
+
+        if (introspData.size() > 1) {
+            return introspData;
+        } else if (introspData.size() == 0) {
+            return Collections.emptyMap();
+        } else { // map.size() == 1
+            Entry<Object, Object> e = introspData.entrySet().iterator().next();
+            return Collections.singletonMap(e.getKey(), e.getValue());
+        }
+    }
+
+    private void addFieldsToClassIntrospectionData(Map<Object, Object> introspData, Class<?> clazz)
+            throws SecurityException {
+        for (Field field : clazz.getFields()) {
+            if ((field.getModifiers() & Modifier.STATIC) == 0) {
+                introspData.put(field.getName(), field);
+            }
+        }
+    }
+
+    private void addBeanInfoToClassIntrospectionData(
+            Map<Object, Object> introspData, Class<?> clazz, Map<MethodSignature, List<Method>> accessibleMethods)
+            throws IntrospectionException {
+        BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
+
+        PropertyDescriptor[] pda = beanInfo.getPropertyDescriptors();
+        if (pda != null) {
+            int pdaLength = pda.length;
+            for (int i = pdaLength - 1; i >= 0; --i) {
+                addPropertyDescriptorToClassIntrospectionData(
+                        introspData, pda[i], clazz,
+                        accessibleMethods);
+            }
+        }
+
+        if (exposureLevel < DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY) {
+            final MethodAppearanceFineTuner.Decision decision = new MethodAppearanceFineTuner.Decision();
+            DecisionInput decisionInput = null;
+            final MethodDescriptor[] mda = sortMethodDescriptors(beanInfo.getMethodDescriptors());
+            if (mda != null) {
+                int mdaLength = mda.length;
+                for (int i = mdaLength - 1; i >= 0; --i) {
+                    final MethodDescriptor md = mda[i];
+                    final Method method = getMatchingAccessibleMethod(md.getMethod(), accessibleMethods);
+                    if (method != null && isAllowedToExpose(method)) {
+                        decision.setDefaults(method);
+                        if (methodAppearanceFineTuner != null) {
+                            if (decisionInput == null) {
+                                decisionInput = new DecisionInput();
+                            }
+                            decisionInput.setContainingClass(clazz);
+                            decisionInput.setMethod(method);
+    
+                            methodAppearanceFineTuner.process(decisionInput, decision);
+                        }
+    
+                        PropertyDescriptor propDesc = decision.getExposeAsProperty();
+                        if (propDesc != null && !(introspData.get(propDesc.getName()) instanceof PropertyDescriptor)) {
+                            addPropertyDescriptorToClassIntrospectionData(
+                                    introspData, propDesc, clazz, accessibleMethods);
+                        }
+    
+                        String methodKey = decision.getExposeMethodAs();
+                        if (methodKey != null) {
+                            Object previous = introspData.get(methodKey);
+                            if (previous instanceof Method) {
+                                // Overloaded method - replace Method with a OverloadedMethods
+                                OverloadedMethods overloadedMethods = new OverloadedMethods();
+                                overloadedMethods.addMethod((Method) previous);
+                                overloadedMethods.addMethod(method);
+                                introspData.put(methodKey, overloadedMethods);
+                                // Remove parameter type information
+                                getArgTypesByMethod(introspData).remove(previous);
+                            } else if (previous instanceof OverloadedMethods) {
+                                // Already overloaded method - add new overload
+                                ((OverloadedMethods) previous).addMethod(method);
+                            } else if (decision.getMethodShadowsProperty()
+                                    || !(previous instanceof PropertyDescriptor)) {
+                                // Simple method (this far)
+                                introspData.put(methodKey, method);
+                                getArgTypesByMethod(introspData).put(method,
+                                        method.getParameterTypes());
+                            }
+                        }
+                    }
+                } // for each in mda
+            } // if mda != null
+        } // end if (exposureLevel < EXPOSE_PROPERTIES_ONLY)
+    }
+
+    private void addPropertyDescriptorToClassIntrospectionData(Map<Object, Object> introspData,
+            PropertyDescriptor pd, Class<?> clazz, Map<MethodSignature, List<Method>> accessibleMethods) {
+        if (pd instanceof IndexedPropertyDescriptor) {
+            IndexedPropertyDescriptor ipd =
+                    (IndexedPropertyDescriptor) pd;
+            Method readMethod = ipd.getIndexedReadMethod();
+            Method publicReadMethod = getMatchingAccessibleMethod(readMethod, accessibleMethods);
+            if (publicReadMethod != null && isAllowedToExpose(publicReadMethod)) {
+                try {
+                    if (readMethod != publicReadMethod) {
+                        ipd = new IndexedPropertyDescriptor(
+                                ipd.getName(), ipd.getReadMethod(),
+                                null, publicReadMethod,
+                                null);
+                    }
+                    introspData.put(ipd.getName(), ipd);
+                    getArgTypesByMethod(introspData).put(publicReadMethod, publicReadMethod.getParameterTypes());
+                } catch (IntrospectionException e) {
+                    LOG.warn("Failed creating a publicly-accessible property descriptor "
+                            + "for {} indexed property {}, read method {}",
+                            clazz.getName(), pd.getName(), publicReadMethod,
+                            e);
+                }
+            }
+        } else {
+            Method readMethod = pd.getReadMethod();
+            Method publicReadMethod = getMatchingAccessibleMethod(readMethod, accessibleMethods);
+            if (publicReadMethod != null && isAllowedToExpose(publicReadMethod)) {
+                try {
+                    if (readMethod != publicReadMethod) {
+                        pd = new PropertyDescriptor(pd.getName(), publicReadMethod, null);
+                        pd.setReadMethod(publicReadMethod);
+                    }
+                    introspData.put(pd.getName(), pd);
+                } catch (IntrospectionException e) {
+                    LOG.warn("Failed creating a publicly-accessible property descriptor "
+                            + "for {} property {}, read method {}",
+                            clazz.getName(), pd.getName(), publicReadMethod,
+                            e);
+                }
+            }
+        }
+    }
+
+    private void addGenericGetToClassIntrospectionData(Map<Object, Object> introspData,
+            Map<MethodSignature, List<Method>> accessibleMethods) {
+        Method genericGet = getFirstAccessibleMethod(
+                MethodSignature.GET_STRING_SIGNATURE, accessibleMethods);
+        if (genericGet == null) {
+            genericGet = getFirstAccessibleMethod(
+                    MethodSignature.GET_OBJECT_SIGNATURE, accessibleMethods);
+        }
+        if (genericGet != null) {
+            introspData.put(GENERIC_GET_KEY, genericGet);
+        }
+    }
+
+    private void addConstructorsToClassIntrospectionData(final Map<Object, Object> introspData,
+            Class<?> clazz) {
+        try {
+            Constructor<?>[] ctors = clazz.getConstructors();
+            if (ctors.length == 1) {
+                Constructor<?> ctor = ctors[0];
+                introspData.put(CONSTRUCTORS_KEY, new SimpleMethod(ctor, ctor.getParameterTypes()));
+            } else if (ctors.length > 1) {
+                OverloadedMethods overloadedCtors = new OverloadedMethods();
+                for (Constructor<?> ctor : ctors) {
+                    overloadedCtors.addConstructor(ctor);
+                }
+                introspData.put(CONSTRUCTORS_KEY, overloadedCtors);
+            }
+        } catch (SecurityException e) {
+            LOG.warn("Can't discover constructors for class {}", clazz.getName(), e);
+        }
+    }
+
+    /**
+     * Retrieves mapping of {@link MethodSignature}-s to a {@link List} of accessible methods for a class. In case the
+     * class is not public, retrieves methods with same signature as its public methods from public superclasses and
+     * interfaces. Basically upcasts every method to the nearest accessible method.
+     */
+    private static Map<MethodSignature, List<Method>> discoverAccessibleMethods(Class<?> clazz) {
+        Map<MethodSignature, List<Method>> accessibles = new HashMap<>();
+        discoverAccessibleMethods(clazz, accessibles);
+        return accessibles;
+    }
+
+    private static void discoverAccessibleMethods(Class<?> clazz, Map<MethodSignature, List<Method>> accessibles) {
+        if (Modifier.isPublic(clazz.getModifiers())) {
+            try {
+                Method[] methods = clazz.getMethods();
+                for (Method method : methods) {
+                    MethodSignature sig = new MethodSignature(method);
+                    // Contrary to intuition, a class can actually have several
+                    // different methods with same signature *but* different
+                    // return types. These can't be constructed using Java the
+                    // language, as this is illegal on source code level, but
+                    // the compiler can emit synthetic methods as part of
+                    // generic type reification that will have same signature
+                    // yet different return type than an existing explicitly
+                    // declared method. Consider:
+                    // public interface I<T> { T m(); }
+                    // public class C implements I<Integer> { Integer m() { return 42; } }
+                    // C.class will have both "Object m()" and "Integer m()" methods.
+                    List<Method> methodList = accessibles.get(sig);
+                    if (methodList == null) {
+                        // TODO Collection.singletonList is more efficient, though read only.
+                        methodList = new LinkedList<>();
+                        accessibles.put(sig, methodList);
+                    }
+                    methodList.add(method);
+                }
+                return;
+            } catch (SecurityException e) {
+                LOG.warn("Could not discover accessible methods of class {}, attemping superclasses/interfaces.",
+                        clazz.getName(), e);
+                // Fall through and attempt to discover superclass/interface methods
+            }
+        }
+
+        Class<?>[] interfaces = clazz.getInterfaces();
+        for (Class<?> anInterface : interfaces) {
+            discoverAccessibleMethods(anInterface, accessibles);
+        }
+        Class<?> superclass = clazz.getSuperclass();
+        if (superclass != null) {
+            discoverAccessibleMethods(superclass, accessibles);
+        }
+    }
+
+    private static Method getMatchingAccessibleMethod(Method m, Map<MethodSignature, List<Method>> accessibles) {
+        if (m == null) {
+            return null;
+        }
+        MethodSignature sig = new MethodSignature(m);
+        List<Method> ams = accessibles.get(sig);
+        if (ams == null) {
+            return null;
+        }
+        for (Method am : ams) {
+            if (am.getReturnType() == m.getReturnType()) {
+                return am;
+            }
+        }
+        return null;
+    }
+
+    private static Method getFirstAccessibleMethod(MethodSignature sig, Map<MethodSignature, List<Method>> accessibles) {
+        List<Method> ams = accessibles.get(sig);
+        if (ams == null || ams.isEmpty()) {
+            return null;
+        }
+        return ams.get(0);
+    }
+
+    /**
+     * As of this writing, this is only used for testing if method order really doesn't mater.
+     */
+    private MethodDescriptor[] sortMethodDescriptors(MethodDescriptor[] methodDescriptors) {
+        return methodSorter != null ? methodSorter.sortMethodDescriptors(methodDescriptors) : methodDescriptors;
+    }
+
+    boolean isAllowedToExpose(Method method) {
+        return exposureLevel < DefaultObjectWrapper.EXPOSE_SAFE || !UnsafeMethods.isUnsafeMethod(method);
+    }
+
+    private static Map<Method, Class<?>[]> getArgTypesByMethod(Map<Object, Object> classInfo) {
+        @SuppressWarnings("unchecked")
+        Map<Method, Class<?>[]> argTypes = (Map<Method, Class<?>[]>) classInfo.get(ARG_TYPES_BY_METHOD_KEY);
+        if (argTypes == null) {
+            argTypes = new HashMap<>();
+            classInfo.put(ARG_TYPES_BY_METHOD_KEY, argTypes);
+        }
+        return argTypes;
+    }
+
+    private static final class MethodSignature {
+        private static final MethodSignature GET_STRING_SIGNATURE =
+                new MethodSignature("get", new Class[] { String.class });
+        private static final MethodSignature GET_OBJECT_SIGNATURE =
+                new MethodSignature("get", new Class[] { Object.class });
+
+        private final String name;
+        private final Class<?>[] args;
+
+        private MethodSignature(String name, Class<?>[] args) {
+            this.name = name;
+            this.args = args;
+        }
+
+        MethodSignature(Method method) {
+            this(method.getName(), method.getParameterTypes());
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof MethodSignature) {
+                MethodSignature ms = (MethodSignature) o;
+                return ms.name.equals(name) && Arrays.equals(args, ms.args);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return name.hashCode() ^ args.length; // TODO That's a poor quality hash... isn't this a problem?
+        }
+    }
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Cache management:
+
+    /**
+     * Corresponds to {@link DefaultObjectWrapper#clearClassIntrospecitonCache()}.
+     * 
+     * @since 2.3.20
+     */
+    void clearCache() {
+        if (getHasSharedInstanceRestrictons()) {
+            throw new IllegalStateException(
+                    "It's not allowed to clear the whole cache in a read-only " + getClass().getName() +
+                            "instance. Use removeFromClassIntrospectionCache(String prefix) instead.");
+        }
+        forcedClearCache();
+    }
+
+    private void forcedClearCache() {
+        synchronized (sharedLock) {
+            cache.clear();
+            cacheClassNames.clear();
+            clearingCounter++;
+
+            for (WeakReference<Object> regedMfREf : modelFactories) {
+                Object regedMf = regedMfREf.get();
+                if (regedMf != null) {
+                    if (regedMf instanceof ClassBasedModelFactory) {
+                        ((ClassBasedModelFactory) regedMf).clearCache();
+                    } else if (regedMf instanceof ModelCache) {
+                        ((ModelCache) regedMf).clearCache();
+                    } else {
+                        throw new BugException();
+                    }
+                }
+            }
+
+            removeClearedModelFactoryReferences();
+        }
+    }
+
+    /**
+     * Corresponds to {@link DefaultObjectWrapper#removeFromClassIntrospectionCache(Class)}.
+     * 
+     * @since 2.3.20
+     */
+    void remove(Class<?> clazz) {
+        synchronized (sharedLock) {
+            cache.remove(clazz);
+            cacheClassNames.remove(clazz.getName());
+            clearingCounter++;
+
+            for (WeakReference<Object> regedMfREf : modelFactories) {
+                Object regedMf = regedMfREf.get();
+                if (regedMf != null) {
+                    if (regedMf instanceof ClassBasedModelFactory) {
+                        ((ClassBasedModelFactory) regedMf).removeFromCache(clazz);
+                    } else if (regedMf instanceof ModelCache) {
+                        ((ModelCache) regedMf).clearCache(); // doesn't support selective clearing ATM
+                    } else {
+                        throw new BugException();
+                    }
+                }
+            }
+
+            removeClearedModelFactoryReferences();
+        }
+    }
+
+    /**
+     * Returns the number of events so far that could make class introspection data returned earlier outdated.
+     */
+    int getClearingCounter() {
+        synchronized (sharedLock) {
+            return clearingCounter;
+        }
+    }
+
+    private void onSameNameClassesDetected(String className) {
+        // TODO: This behavior should be pluggable, as in environments where
+        // some classes are often reloaded or multiple versions of the
+        // same class is normal (OSGi), this will drop the cache contents
+        // too often.
+        LOG.info(
+                "Detected multiple classes with the same name, \"{}\". "
+                + "Assuming it was a class-reloading. Clearing class introspection caches to release old data.",
+                className);
+        forcedClearCache();
+    }
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Managing dependent objects:
+
+    void registerModelFactory(ClassBasedModelFactory mf) {
+        registerModelFactory((Object) mf);
+    }
+
+    void registerModelFactory(ModelCache mf) {
+        registerModelFactory((Object) mf);
+    }
+
+    private void registerModelFactory(Object mf) {
+        // Note that this `synchronized (sharedLock)` is also need for the DefaultObjectWrapper constructor to work safely.
+        synchronized (sharedLock) {
+            modelFactories.add(new WeakReference<>(mf, modelFactoriesRefQueue));
+            removeClearedModelFactoryReferences();
+        }
+    }
+
+    void unregisterModelFactory(ClassBasedModelFactory mf) {
+        unregisterModelFactory((Object) mf);
+    }
+
+    void unregisterModelFactory(ModelCache mf) {
+        unregisterModelFactory((Object) mf);
+    }
+
+    void unregisterModelFactory(Object mf) {
+        synchronized (sharedLock) {
+            for (Iterator<WeakReference<Object>> it = modelFactories.iterator(); it.hasNext(); ) {
+                Object regedMf = it.next().get();
+                if (regedMf == mf) {
+                    it.remove();
+                }
+            }
+
+        }
+    }
+
+    private void removeClearedModelFactoryReferences() {
+        Reference<?> cleardRef;
+        while ((cleardRef = modelFactoriesRefQueue.poll()) != null) {
+            synchronized (sharedLock) {
+                findClearedRef: for (Iterator<WeakReference<Object>> it = modelFactories.iterator(); it.hasNext(); ) {
+                    if (it.next() == cleardRef) {
+                        it.remove();
+                        break findClearedRef;
+                    }
+                }
+            }
+        }
+    }
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Extracting from introspection info:
+
+    static Class<?>[] getArgTypes(Map<Object, Object> classInfo, Method method) {
+        @SuppressWarnings("unchecked")
+        Map<Method, Class<?>[]> argTypesByMethod = (Map<Method, Class<?>[]>) classInfo.get(ARG_TYPES_BY_METHOD_KEY);
+        return argTypesByMethod.get(method);
+    }
+
+    /**
+     * Returns the number of introspected methods/properties that should be available via the TemplateHashModel
+     * interface.
+     */
+    int keyCount(Class<?> clazz) {
+        Map<Object, Object> map = get(clazz);
+        int count = map.size();
+        if (map.containsKey(CONSTRUCTORS_KEY)) count--;
+        if (map.containsKey(GENERIC_GET_KEY)) count--;
+        if (map.containsKey(ARG_TYPES_BY_METHOD_KEY)) count--;
+        return count;
+    }
+
+    /**
+     * Returns the Set of names of introspected methods/properties that should be available via the TemplateHashModel
+     * interface.
+     */
+    Set<Object> keySet(Class<?> clazz) {
+        Set<Object> set = new HashSet<>(get(clazz).keySet());
+        set.remove(CONSTRUCTORS_KEY);
+        set.remove(GENERIC_GET_KEY);
+        set.remove(ARG_TYPES_BY_METHOD_KEY);
+        return set;
+    }
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Properties
+
+    int getExposureLevel() {
+        return exposureLevel;
+    }
+
+    boolean getExposeFields() {
+        return exposeFields;
+    }
+
+    MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
+        return methodAppearanceFineTuner;
+    }
+
+    MethodSorter getMethodSorter() {
+        return methodSorter;
+    }
+
+    /**
+     * Returns {@code true} if this instance was created with {@link ClassIntrospectorBuilder}, even if it wasn't
+     * actually put into the cache (as we reserve the right to do so in later versions).
+     */
+    boolean getHasSharedInstanceRestrictons() {
+        return hasSharedInstanceRestrictons;
+    }
+
+    /**
+     * Tells if this instance is (potentially) shared among {@link DefaultObjectWrapper} instances.
+     * 
+     * @see #getHasSharedInstanceRestrictons()
+     */
+    boolean isShared() {
+        return shared;
+    }
+
+    /**
+     * Almost always, you want to use {@link DefaultObjectWrapper#getSharedIntrospectionLock()}, not this! The only exception is
+     * when you get this to set the field returned by {@link DefaultObjectWrapper#getSharedIntrospectionLock()}.
+     */
+    Object getSharedLock() {
+        return sharedLock;
+    }
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Monitoring:
+
+    /** For unit testing only */
+    Object[] getRegisteredModelFactoriesSnapshot() {
+        synchronized (sharedLock) {
+            return modelFactories.toArray();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..b17a49e
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospectorBuilder.java
@@ -0,0 +1,190 @@
+/*
+ * 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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/CollectionAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/CollectionAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/CollectionAdapter.java
new file mode 100644
index 0000000..70caa66
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/CollectionAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ * Adapts a {@link TemplateCollectionModel} to  {@link Collection}.
+ */
+class CollectionAdapter extends AbstractCollection implements TemplateModelAdapter {
+    private final DefaultObjectWrapper wrapper;
+    private final TemplateCollectionModel model;
+    
+    CollectionAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) {
+        this.model = model;
+        this.wrapper = wrapper;
+    }
+    
+    @Override
+    public TemplateModel getTemplateModel() {
+        return model;
+    }
+    
+    @Override
+    public int size() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Iterator iterator() {
+        try {
+            return new Iterator() {
+                final TemplateModelIterator i = model.iterator();
+    
+                @Override
+                public boolean hasNext() {
+                    try {
+                        return i.hasNext();
+                    } catch (TemplateModelException e) {
+                        throw new UndeclaredThrowableException(e);
+                    }
+                }
+                
+                @Override
+                public Object next() {
+                    try {
+                        return wrapper.unwrap(i.next());
+                    } catch (TemplateModelException e) {
+                        throw new UndeclaredThrowableException(e);
+                    }
+                }
+                
+                @Override
+                public void remove() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        } catch (TemplateModelException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/CollectionModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/CollectionModel.java b/src/main/java/org/apache/freemarker/core/model/impl/CollectionModel.java
new file mode 100644
index 0000000..36c6947
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/CollectionModel.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+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.TemplateSequenceModel;
+
+/**
+ * <p>A special case of {@link BeanModel} that can wrap Java collections
+ * and that implements the {@link TemplateCollectionModel} in order to be usable 
+ * in a <tt>&lt;#list&gt;</tt> block.</p>
+ */
+public class CollectionModel
+extends
+    StringModel
+implements
+    TemplateCollectionModel,
+    TemplateSequenceModel {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new CollectionModel((Collection) object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+
+
+    /**
+     * Creates a new model that wraps the specified collection object.
+     * @param collection the collection object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     */
+    public CollectionModel(Collection collection, DefaultObjectWrapper wrapper) {
+        super(collection, wrapper);
+    }
+
+    /**
+     * Retrieves the i-th object from the collection, wrapped as a TemplateModel.
+     * @throws TemplateModelException if the index is out of bounds, or the
+     * underlying collection is not a List.
+     */
+    @Override
+    public TemplateModel get(int index)
+    throws TemplateModelException {
+        // Don't forget to keep getSupportsIndexedAccess in sync with this!
+        if (object instanceof List) {
+            try {
+                return wrap(((List) object).get(index));
+            } catch (IndexOutOfBoundsException e) {
+                return null;
+//                throw new TemplateModelException("Index out of bounds: " + index);
+            }
+        } else {
+            throw new TemplateModelException("Underlying collection is not a list, it's " + object.getClass().getName());
+        }
+    }
+    
+    /**
+     * Tells if {@link #get(int)} will always fail for this object.
+     * As this object implements {@link TemplateSequenceModel},
+     * {@link #get(int)} should always work, but due to a design flaw, for
+     * non-{@link List} wrapped objects {@link #get(int)} will always fail.
+     * This method exists to ease working this problem around.
+     * 
+     * @since 2.3.17 
+     */
+    public boolean getSupportsIndexedAccess() {
+        return object instanceof List;
+    }
+    
+    @Override
+    public TemplateModelIterator iterator() {
+        return new IteratorModel(((Collection) object).iterator(), wrapper);
+    }
+
+    @Override
+    public int size() {
+        return ((Collection) object).size();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/DateModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DateModel.java b/src/main/java/org/apache/freemarker/core/model/impl/DateModel.java
new file mode 100644
index 0000000..e16e375
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DateModel.java
@@ -0,0 +1,76 @@
+/*
+ * 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.util.Date;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Wraps arbitrary subclass of {@link java.util.Date} into a reflective model.
+ * Beside acting as a {@link TemplateDateModel}, you can call all Java methods
+ * on these objects as well.
+ */
+public class DateModel extends BeanModel implements
+    TemplateDateModel {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new DateModel((Date) object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+
+    private final int type;
+    
+    /**
+     * Creates a new model that wraps the specified date object.
+     * @param date the date object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     */
+    public DateModel(Date date, DefaultObjectWrapper wrapper) {
+        super(date, wrapper);
+        if (date instanceof java.sql.Date) {
+            type = DATE;
+        } else if (date instanceof java.sql.Time) {
+            type = TIME;
+        } else if (date instanceof java.sql.Timestamp) {
+            type = DATETIME;
+        } else {
+            type = wrapper.getDefaultDateType();
+        }
+    }
+
+    @Override
+    public Date getAsDate() {
+        return (Date) object;
+    }
+
+    @Override
+    public int getDateType() {
+        return type;
+    }
+}



[09/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/PrimtiveArrayBackedReadOnlyList.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/PrimtiveArrayBackedReadOnlyList.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/PrimtiveArrayBackedReadOnlyList.java
deleted file mode 100644
index 2e8850d..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/PrimtiveArrayBackedReadOnlyList.java
+++ /dev/null
@@ -1,47 +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.beans;
-
-import java.lang.reflect.Array;
-import java.util.AbstractList;
-
-/**
- * Similar to {@link NonPrimitiveArrayBackedReadOnlyList}, but uses reflection so that it works with primitive arrays
- * too. 
- */
-class PrimtiveArrayBackedReadOnlyList extends AbstractList {
-    
-    private final Object array;
-    
-    PrimtiveArrayBackedReadOnlyList(Object array) {
-        this.array = array;
-    }
-
-    @Override
-    public Object get(int index) {
-        return Array.get(array, index);
-    }
-
-    @Override
-    public int size() {
-        return Array.getLength(array);
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ReflectionCallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ReflectionCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ReflectionCallableMemberDescriptor.java
deleted file mode 100644
index 5cf0676..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ReflectionCallableMemberDescriptor.java
+++ /dev/null
@@ -1,95 +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.beans;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- * The most commonly used {@link CallableMemberDescriptor} implementation. 
- */
-final class ReflectionCallableMemberDescriptor extends CallableMemberDescriptor {
-
-    private final Member/*Method|Constructor*/ member;
-    
-    /**
-     * Don't modify this array!
-     */
-    final Class[] paramTypes;
-    
-    ReflectionCallableMemberDescriptor(Method member, Class[] paramTypes) {
-        this.member = member;
-        this.paramTypes = paramTypes;
-    }
-
-    ReflectionCallableMemberDescriptor(Constructor member, Class[] paramTypes) {
-        this.member = member;
-        this.paramTypes = paramTypes;
-    }
-
-    @Override
-    TemplateModel invokeMethod(BeansWrapper bw, Object obj, Object[] args)
-            throws TemplateModelException, InvocationTargetException, IllegalAccessException {
-        return bw.invokeMethod(obj, (Method) member, args);
-    }
-
-    @Override
-    Object invokeConstructor(BeansWrapper bw, Object[] args)
-            throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
-        return ((Constructor) member).newInstance(args);
-    }
-
-    @Override
-    String getDeclaration() {
-        return _MethodUtil.toString(member);
-    }
-    
-    @Override
-    boolean isConstructor() {
-        return member instanceof Constructor;
-    }
-    
-    @Override
-    boolean isStatic() {
-        return (member.getModifiers() & Modifier.STATIC) != 0;
-    }
-
-    @Override
-    boolean isVarargs() {
-        return _MethodUtil.isVarargs(member);
-    }
-
-    @Override
-    Class[] getParamTypes() {
-        return paramTypes;
-    }
-
-    @Override
-    String getName() {
-        return member.getName();
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ResourceBundleModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ResourceBundleModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ResourceBundleModel.java
deleted file mode 100644
index a79e786..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ResourceBundleModel.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.beans;
-
-import java.text.MessageFormat;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
-import java.util.Set;
-
-import org.apache.freemarker.core._DelayedJQuote;
-import org.apache.freemarker.core._TemplateModelException;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- * <p>A hash model that wraps a resource bundle. Makes it convenient to store
- * localized content in the data model. It also acts as a method model that will
- * take a resource key and arbitrary number of arguments and will apply
- * {@link MessageFormat} with arguments on the string represented by the key.</p>
- *
- * <p>Typical usages:</p>
- * <ul>
- * <li><tt>bundle.resourceKey</tt> will retrieve the object from resource bundle
- * with key <tt>resourceKey</tt></li>
- * <li><tt>bundle("patternKey", arg1, arg2, arg3)</tt> will retrieve the string
- * from resource bundle with key <tt>patternKey</tt>, and will use it as a pattern
- * for MessageFormat with arguments arg1, arg2 and arg3</li>
- * </ul>
- */
-public class ResourceBundleModel
-    extends
-    BeanModel
-    implements
-    TemplateMethodModelEx {
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new ResourceBundleModel((ResourceBundle) object, (BeansWrapper) wrapper);
-            }
-        };
-
-    private Hashtable formats = null;
-
-    public ResourceBundleModel(ResourceBundle bundle, BeansWrapper wrapper) {
-        super(bundle, wrapper);
-    }
-
-    /**
-     * Overridden to invoke the getObject method of the resource bundle.
-     */
-    @Override
-    protected TemplateModel invokeGenericGet(Map keyMap, Class clazz, String key)
-    throws TemplateModelException {
-        try {
-            return wrap(((ResourceBundle) object).getObject(key));
-        } catch (MissingResourceException e) {
-            throw new _TemplateModelException(e,
-                    "No ", new _DelayedJQuote(key), " key in the ResourceBundle. "
-                    + "Note that conforming to the ResourceBundle Java API, this is an error and not just "
-                    + "a missing sub-variable (a null).");
-        }
-    }
-
-    /**
-     * Returns true if this bundle contains no objects.
-     */
-    @Override
-    public boolean isEmpty() {
-        return !((ResourceBundle) object).getKeys().hasMoreElements() &&
-            super.isEmpty();
-    }
-
-    @Override
-    public int size() {
-        return keySet().size();
-    }
-
-    @Override
-    protected Set keySet() {
-        Set set = super.keySet();
-        Enumeration e = ((ResourceBundle) object).getKeys();
-        while (e.hasMoreElements()) {
-            set.add(e.nextElement());
-        }
-        return set;
-    }
-
-    /**
-     * Takes first argument as a resource key, looks up a string in resource bundle
-     * with this key, then applies a MessageFormat.format on the string with the
-     * rest of the arguments. The created MessageFormats are cached for later reuse.
-     */
-    @Override
-    public Object exec(List arguments)
-        throws TemplateModelException {
-        // Must have at least one argument - the key
-        if (arguments.size() < 1)
-            throw new TemplateModelException("No message key was specified");
-        // Read it
-        Iterator it = arguments.iterator();
-        String key = unwrap((TemplateModel) it.next()).toString();
-        try {
-            if (!it.hasNext()) {
-                return wrap(((ResourceBundle) object).getObject(key));
-            }
-    
-            // Copy remaining arguments into an Object[]
-            int args = arguments.size() - 1;
-            Object[] params = new Object[args];
-            for (int i = 0; i < args; ++i)
-                params[i] = unwrap((TemplateModel) it.next());
-    
-            // Invoke format
-            return new StringModel(format(key, params), wrapper);
-        } catch (MissingResourceException e) {
-            throw new TemplateModelException("No such key: " + key);
-        } catch (Exception e) {
-            throw new TemplateModelException(e.getMessage());
-        }
-    }
-
-    /**
-     * Provides direct access to caching format engine from code (instead of from script).
-     */
-    public String format(String key, Object[] params)
-        throws MissingResourceException {
-        // Check to see if we already have a cache for message formats
-        // and construct it if we don't
-        // NOTE: this block statement should be synchronized. However
-        // concurrent creation of two caches will have no harmful
-        // consequences, and we avoid a performance hit.
-        /* synchronized(this) */
-        {
-            if (formats == null)
-                formats = new Hashtable();
-        }
-
-        MessageFormat format = null;
-        // Check to see if we already have a requested MessageFormat cached
-        // and construct it if we don't
-        // NOTE: this block statement should be synchronized. However
-        // concurrent creation of two formats will have no harmful
-        // consequences, and we avoid a performance hit.
-        /* synchronized(formats) */
-        {
-            format = (MessageFormat) formats.get(key);
-            if (format == null) {
-                format = new MessageFormat(((ResourceBundle) object).getString(key));
-                format.setLocale(getBundle().getLocale());
-                formats.put(key, format);
-            }
-        }
-
-        // Perform the formatting. We synchronize on it in case it
-        // contains date formatting, which is not thread-safe.
-        synchronized (format) {
-            return format.format(params);
-        }
-    }
-
-    public ResourceBundle getBundle() {
-        return (ResourceBundle) object;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/SequenceAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/SequenceAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/SequenceAdapter.java
deleted file mode 100644
index 7f66cf9..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/SequenceAdapter.java
+++ /dev/null
@@ -1,68 +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.beans;
-
-import java.util.AbstractList;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-
-/**
- */
-class SequenceAdapter extends AbstractList implements TemplateModelAdapter {
-    private final BeansWrapper wrapper;
-    private final TemplateSequenceModel model;
-    
-    SequenceAdapter(TemplateSequenceModel model, BeansWrapper wrapper) {
-        this.model = model;
-        this.wrapper = wrapper;
-    }
-    
-    @Override
-    public TemplateModel getTemplateModel() {
-        return model;
-    }
-    
-    @Override
-    public int size() {
-        try {
-            return model.size();
-        } catch (TemplateModelException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-    
-    @Override
-    public Object get(int index) {
-        try {
-            return wrapper.unwrap(model.get(index));
-        } catch (TemplateModelException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-    
-    public TemplateSequenceModel getTemplateSequenceModel() {
-        return model;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/SetAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/SetAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/SetAdapter.java
deleted file mode 100644
index 2d65efa..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/SetAdapter.java
+++ /dev/null
@@ -1,32 +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.beans;
-
-import java.util.Set;
-
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-
-/**
- */
-class SetAdapter extends CollectionAdapter implements Set {
-    SetAdapter(TemplateCollectionModel model, BeansWrapper wrapper) {
-        super(model, wrapper);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMapModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMapModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMapModel.java
deleted file mode 100644
index 80e04f7..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMapModel.java
+++ /dev/null
@@ -1,132 +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.beans;
-
-import java.util.List;
-import java.util.Map;
-
-import org.apache.freemarker.core.model.AdapterTemplateModel;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.RichObjectWrapper;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx2;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
-import org.apache.freemarker.core.model.WrapperTemplateModel;
-import org.apache.freemarker.core.model.WrappingTemplateModel;
-import org.apache.freemarker.core.model.impl.CollectionAndSequence;
-import org.apache.freemarker.core.model.impl.MapKeyValuePairIterator;
-import org.apache.freemarker.core.model.impl.SimpleSequence;
-
-/**
- * Model used by {@link BeansWrapper} when <tt>simpleMapWrapper</tt>
- * mode is enabled. Provides a simple hash model interface to the
- * underlying map (does not copy like {@link org.apache.freemarker.core.model.impl.SimpleHash}),
- * and a method interface to non-string keys.
- */
-public class SimpleMapModel extends WrappingTemplateModel 
-implements TemplateHashModelEx2, TemplateMethodModelEx, AdapterTemplateModel, 
-WrapperTemplateModel, TemplateModelWithAPISupport {
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new SimpleMapModel((Map) object, (BeansWrapper) wrapper);
-            }
-        };
-
-    private final Map map;
-    
-    public SimpleMapModel(Map map, BeansWrapper wrapper) {
-        super(wrapper);
-        this.map = map;
-    }
-
-    @Override
-    public TemplateModel get(String key) throws TemplateModelException {
-        Object val = map.get(key);
-        if (val == null) {
-            if (key.length() == 1) {
-                // just check for Character key if this is a single-character string
-                Character charKey = Character.valueOf(key.charAt(0));
-                val = map.get(charKey);
-                if (val == null && !(map.containsKey(key) || map.containsKey(charKey))) {
-                    return null;
-                }
-            } else if (!map.containsKey(key)) {
-                return null;
-            }
-        }
-        return wrap(val);
-    }
-    
-    @Override
-    public Object exec(List args) throws TemplateModelException {
-        Object key = ((BeansWrapper) getObjectWrapper()).unwrap((TemplateModel) args.get(0));
-        Object value = map.get(key);
-        if (value == null && !map.containsKey(key)) {
-            return null;
-        }
-        return wrap(value);
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return map.isEmpty();
-    }
-
-    @Override
-    public int size() {
-        return map.size();
-    }
-
-    @Override
-    public TemplateCollectionModel keys() {
-        return new CollectionAndSequence(new SimpleSequence(map.keySet(), getObjectWrapper()));
-    }
-
-    @Override
-    public TemplateCollectionModel values() {
-        return new CollectionAndSequence(new SimpleSequence(map.values(), getObjectWrapper()));
-    }
-    
-    @Override
-    public KeyValuePairIterator keyValuePairIterator() {
-        return new MapKeyValuePairIterator(map, getObjectWrapper());
-    }
-
-    @Override
-    public Object getAdaptedObject(Class hint) {
-        return map;
-    }
-    
-    @Override
-    public Object getWrappedObject() {
-        return map;
-    }
-
-    @Override
-    public TemplateModel getAPI() throws TemplateModelException {
-        return ((RichObjectWrapper) getObjectWrapper()).wrapAsAPI(map);
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMethod.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMethod.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMethod.java
deleted file mode 100644
index f863486..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMethod.java
+++ /dev/null
@@ -1,174 +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.beans;
-
-import java.lang.reflect.Array;
-import java.lang.reflect.Member;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.freemarker.core._DelayedFTLTypeDescription;
-import org.apache.freemarker.core._DelayedOrdinal;
-import org.apache.freemarker.core._ErrorDescriptionBuilder;
-import org.apache.freemarker.core._TemplateModelException;
-import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
-import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.util._ClassUtil;
-
-/**
- * This class is used for as a base for non-overloaded method models and for constructors.
- * (For overloaded methods and constructors see {@link OverloadedMethods}.)
- */
-class SimpleMethod {
-    
-    static final String MARKUP_OUTPUT_TO_STRING_TIP
-            = "A markup output value can be converted to markup string like value?markup_string. "
-              + "But consider if the Java method whose argument it will be can handle markup strings properly.";
-    
-    private final Member member;
-    private final Class[] argTypes;
-    
-    protected SimpleMethod(Member member, Class[] argTypes) {
-        this.member = member;
-        this.argTypes = argTypes;
-    }
-    
-    Object[] unwrapArguments(List arguments, BeansWrapper wrapper) throws TemplateModelException {
-        if (arguments == null) {
-            arguments = Collections.EMPTY_LIST;
-        }
-        boolean isVarArg = _MethodUtil.isVarargs(member);
-        int typesLen = argTypes.length;
-        if (isVarArg) {
-            if (typesLen - 1 > arguments.size()) {
-                throw new _TemplateModelException(
-                        _MethodUtil.invocationErrorMessageStart(member),
-                        " takes at least ", Integer.valueOf(typesLen - 1),
-                        typesLen - 1 == 1 ? " argument" : " arguments", ", but ",
-                        Integer.valueOf(arguments.size()), " was given.");
-            }
-        } else if (typesLen != arguments.size()) {
-            throw new _TemplateModelException(
-                    _MethodUtil.invocationErrorMessageStart(member), 
-                    " takes ", Integer.valueOf(typesLen), typesLen == 1 ? " argument" : " arguments", ", but ",
-                    Integer.valueOf(arguments.size()), " was given.");
-        }
-         
-        return unwrapArguments(arguments, argTypes, isVarArg, wrapper);
-    }
-
-    private Object[] unwrapArguments(List args, Class[] argTypes, boolean isVarargs,
-            BeansWrapper w) 
-    throws TemplateModelException {
-        if (args == null) return null;
-        
-        int typesLen = argTypes.length;
-        int argsLen = args.size();
-        
-        Object[] unwrappedArgs = new Object[typesLen];
-        
-        // Unwrap arguments:
-        Iterator it = args.iterator();
-        int normalArgCnt = isVarargs ? typesLen - 1 : typesLen; 
-        int argIdx = 0;
-        while (argIdx < normalArgCnt) {
-            Class argType = argTypes[argIdx];
-            TemplateModel argVal = (TemplateModel) it.next();
-            Object unwrappedArgVal = w.tryUnwrapTo(argVal, argType);
-            if (unwrappedArgVal == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-                throw createArgumentTypeMismarchException(argIdx, argVal, argType);
-            }
-            if (unwrappedArgVal == null && argType.isPrimitive()) {
-                throw createNullToPrimitiveArgumentException(argIdx, argType); 
-            }
-            
-            unwrappedArgs[argIdx++] = unwrappedArgVal;
-        }
-        if (isVarargs) {
-            // The last argType, which is the vararg type, wasn't processed yet.
-            
-            Class varargType = argTypes[typesLen - 1];
-            Class varargItemType = varargType.getComponentType();
-            if (!it.hasNext()) {
-                unwrappedArgs[argIdx++] = Array.newInstance(varargItemType, 0);
-            } else {
-                TemplateModel argVal = (TemplateModel) it.next();
-                
-                Object unwrappedArgVal;
-                // We first try to treat the last argument as a vararg *array*.
-                // This is consistent to what OverloadedVarArgMethod does.
-                if (argsLen - argIdx == 1
-                        && (unwrappedArgVal = w.tryUnwrapTo(argVal, varargType))
-                            != ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-                    // It was a vararg array.
-                    unwrappedArgs[argIdx++] = unwrappedArgVal;
-                } else {
-                    // It wasn't a vararg array, so we assume it's a vararg
-                    // array *item*, possibly followed by further ones.
-                    int varargArrayLen = argsLen - argIdx;
-                    Object varargArray = Array.newInstance(varargItemType, varargArrayLen);
-                    for (int varargIdx = 0; varargIdx < varargArrayLen; varargIdx++) {
-                        TemplateModel varargVal = (TemplateModel) (varargIdx == 0 ? argVal : it.next());
-                        Object unwrappedVarargVal = w.tryUnwrapTo(varargVal, varargItemType);
-                        if (unwrappedVarargVal == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-                            throw createArgumentTypeMismarchException(
-                                    argIdx + varargIdx, varargVal, varargItemType);
-                        }
-                        
-                        if (unwrappedVarargVal == null && varargItemType.isPrimitive()) {
-                            throw createNullToPrimitiveArgumentException(argIdx + varargIdx, varargItemType); 
-                        }
-                        Array.set(varargArray, varargIdx, unwrappedVarargVal);
-                    }
-                    unwrappedArgs[argIdx++] = varargArray;
-                }
-            }
-        }
-        
-        return unwrappedArgs;
-    }
-
-    private TemplateModelException createArgumentTypeMismarchException(
-            int argIdx, TemplateModel argVal, Class targetType) {
-        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: Can't convert the ",
-                new _DelayedOrdinal(Integer.valueOf(argIdx + 1)),
-                " argument's value to the target Java type, ", _ClassUtil.getShortClassName(targetType),
-                ". The type of the actual value was: ", new _DelayedFTLTypeDescription(argVal));
-        if (argVal instanceof TemplateMarkupOutputModel && (targetType.isAssignableFrom(String.class))) {
-            desc.tip(MARKUP_OUTPUT_TO_STRING_TIP);
-        }
-        return new _TemplateModelException(desc);
-    }
-
-    private TemplateModelException createNullToPrimitiveArgumentException(int argIdx, Class targetType) {
-        return new _TemplateModelException(
-                _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: The value of the ",
-                new _DelayedOrdinal(Integer.valueOf(argIdx + 1)),
-                " argument was null, but the target Java parameter type (", _ClassUtil.getShortClassName(targetType),
-                ") is primitive and so can't store null.");
-    }
-    
-    protected Member getMember() {
-        return member;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMethodModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMethodModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMethodModel.java
deleted file mode 100644
index 32c2bc2..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/SimpleMethodModel.java
+++ /dev/null
@@ -1,134 +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.beans;
-
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.impl.SimpleNumber;
-import org.apache.freemarker.core.util.FTLUtil;
-
-/**
- * A class that will wrap a reflected method call into a
- * {@link org.apache.freemarker.core.model.TemplateMethodModel} interface. 
- * It is used by {@link BeanModel} to wrap reflected method calls
- * for non-overloaded methods.
- */
-public final class SimpleMethodModel extends SimpleMethod
-    implements
-    TemplateMethodModelEx,
-    TemplateSequenceModel,
-    _UnexpectedTypeErrorExplainerTemplateModel {
-    private final Object object;
-    private final BeansWrapper wrapper;
-
-    /**
-     * Creates a model for a specific method on a specific object.
-     * @param object the object to call the method on, or {@code null} for a static method.
-     * @param method the method that will be invoked.
-     * @param argTypes Either pass in {@code Method#getParameterTypes() method.getParameterTypes()} here,
-     *          or reuse an earlier result of that call (for speed). Not {@code null}.
-     */
-    SimpleMethodModel(Object object, Method method, Class[] argTypes, 
-            BeansWrapper wrapper) {
-        super(method, argTypes);
-        this.object = object;
-        this.wrapper = wrapper;
-    }
-
-    /**
-     * Invokes the method, passing it the arguments from the list.
-     */
-    @Override
-    public Object exec(List arguments)
-        throws TemplateModelException {
-        try {
-            return wrapper.invokeMethod(object, (Method) getMember(), 
-                    unwrapArguments(arguments, wrapper));
-        } catch (TemplateModelException e) {
-            throw e;
-        } catch (Exception e) {
-            throw _MethodUtil.newInvocationTemplateModelException(object, getMember(), e);
-        }
-    }
-    
-    @Override
-    public TemplateModel get(int index) throws TemplateModelException {
-        return (TemplateModel) exec(Collections.singletonList(
-                new SimpleNumber(Integer.valueOf(index))));
-    }
-
-    @Override
-    public int size() throws TemplateModelException {
-        throw new TemplateModelException(
-                "Getting the number of items or enumerating the items is not supported on this "
-                + FTLUtil.getTypeDescription(this) + " value.\n"
-                + "("
-                + "Hint 1: Maybe you wanted to call this method first and then do something with its return value. "
-                + "Hint 2: Getting items by intex possibly works, hence it's a \"+sequence\"."
-                + ")");
-    }
-    
-    @Override
-    public String toString() {
-        return getMember().toString();
-    }
-
-    /**
-     * Implementation of experimental interface; don't use it, no backward compatibility guarantee!
-     */
-    @Override
-    public Object[] explainTypeError(Class[] expectedClasses) {
-        final Member member = getMember();
-        if (!(member instanceof Method)) {
-            return null;  // This shouldn't occur
-        }
-        Method m = (Method) member;
-        
-        final Class returnType = m.getReturnType();
-        if (returnType == null || returnType == void.class || returnType == Void.class) {
-            return null;  // Calling it won't help
-        }
-        
-        String mName = m.getName();
-        if (mName.startsWith("get") && mName.length() > 3 && Character.isUpperCase(mName.charAt(3))
-                && (m.getParameterTypes().length == 0)) {
-            return new Object[] {
-                    "Maybe using obj.something instead of obj.getSomething will yield the desired value." };
-        } else if (mName.startsWith("is") && mName.length() > 2 && Character.isUpperCase(mName.charAt(2))
-                && (m.getParameterTypes().length == 0)) {
-            return new Object[] {
-                    "Maybe using obj.something instead of obj.isSomething will yield the desired value." };
-        } else {
-            return new Object[] {
-                    "Maybe using obj.something(",
-                    (m.getParameterTypes().length != 0 ? "params" : ""),
-                    ") instead of obj.something will yield the desired value" };
-        }
-    }
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/SingletonCustomizer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/SingletonCustomizer.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/SingletonCustomizer.java
deleted file mode 100644
index df2a6c3..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/SingletonCustomizer.java
+++ /dev/null
@@ -1,51 +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.beans;
-
-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,
- * 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 {
- *      
- *    // This is the singleton:
- *    static final MyMethodAppearanceFineTuner INSTANCE = new MyMethodAppearanceFineTuner();
- *     
- *    // Private, so it can't be constructed from outside this class.
- *    private MyMethodAppearanceFineTuner() { }
- *
- *    &#64;Override
- *    public void fineTuneMethodAppearance(...) {
- *       // Do something here, only using the parameters and maybe some other singletons. 
- *       ...
- *    }
- *     
- * }</pre>
- *
- * @since 2.3.21
- */
-public interface SingletonCustomizer {
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/StaticModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/StaticModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/StaticModel.java
deleted file mode 100644
index a302d8b..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/StaticModel.java
+++ /dev/null
@@ -1,177 +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.beans;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-import org.apache.freemarker.core._CoreLogs;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.slf4j.Logger;
-
-/**
- * Wraps the static fields and methods of a class in a
- * {@link org.apache.freemarker.core.model.TemplateHashModel}.
- * Fields are wrapped using {@link BeansWrapper#wrap(Object)}, and
- * methods are wrapped into an appropriate {@link org.apache.freemarker.core.model.TemplateMethodModelEx} instance.
- * Unfortunately, there is currently no support for bean property-style
- * calls of static methods, similar to that in {@link BeanModel}.
- */
-final class StaticModel implements TemplateHashModelEx {
-    
-    private static final Logger LOG = _CoreLogs.BEANS_WRAPPER;
-    
-    private final Class clazz;
-    private final BeansWrapper wrapper;
-    private final Map map = new HashMap();
-
-    StaticModel(Class clazz, BeansWrapper wrapper) throws TemplateModelException {
-        this.clazz = clazz;
-        this.wrapper = wrapper;
-        populate();
-    }
-
-    /**
-     * Returns the field or method named by the <tt>key</tt>
-     * parameter.
-     */
-    @Override
-    public TemplateModel get(String key) throws TemplateModelException {
-        Object model = map.get(key);
-        // Simple method, overloaded method or final field -- these have cached 
-        // template models
-        if (model instanceof TemplateModel)
-            return (TemplateModel) model;
-        // Non-final field; this must be evaluated on each call.
-        if (model instanceof Field) {
-            try {
-                return wrapper.getOuterIdentity().wrap(((Field) model).get(null));
-            } catch (IllegalAccessException e) {
-                throw new TemplateModelException(
-                    "Illegal access for field " + key + " of class " + clazz.getName());
-            }
-        }
-
-        throw new TemplateModelException(
-            "No such key: " + key + " in class " + clazz.getName());
-    }
-
-    /**
-     * Returns true if there is at least one public static
-     * field or method in the underlying class.
-     */
-    @Override
-    public boolean isEmpty() {
-        return map.isEmpty();
-    }
-
-    @Override
-    public int size() {
-        return map.size();
-    }
-    
-    @Override
-    public TemplateCollectionModel keys() throws TemplateModelException {
-        return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.keySet());
-    }
-    
-    @Override
-    public TemplateCollectionModel values() throws TemplateModelException {
-        return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.values());
-    }
-
-    private void populate() throws TemplateModelException {
-        if (!Modifier.isPublic(clazz.getModifiers())) {
-            throw new TemplateModelException(
-                "Can't wrap the non-public class " + clazz.getName());
-        }
-        
-        if (wrapper.getExposureLevel() == BeansWrapper.EXPOSE_NOTHING) {
-            return;
-        }
-
-        Field[] fields = clazz.getFields();
-        for (Field field : fields) {
-            int mod = field.getModifiers();
-            if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) {
-                if (Modifier.isFinal(mod))
-                    try {
-                        // public static final fields are evaluated once and
-                        // stored in the map
-                        map.put(field.getName(), wrapper.getOuterIdentity().wrap(field.get(null)));
-                    } catch (IllegalAccessException e) {
-                        // Intentionally ignored
-                    }
-                else
-                    // This is a special flagging value: Field in the map means
-                    // that this is a non-final field, and it must be evaluated
-                    // on each get() call.
-                    map.put(field.getName(), field);
-            }
-        }
-        if (wrapper.getExposureLevel() < BeansWrapper.EXPOSE_PROPERTIES_ONLY) {
-            Method[] methods = clazz.getMethods();
-            for (Method method : methods) {
-                int mod = method.getModifiers();
-                if (Modifier.isPublic(mod) && Modifier.isStatic(mod)
-                        && wrapper.getClassIntrospector().isAllowedToExpose(method)) {
-                    String name = method.getName();
-                    Object obj = map.get(name);
-                    if (obj instanceof Method) {
-                        OverloadedMethods overloadedMethods = new OverloadedMethods();
-                        overloadedMethods.addMethod((Method) obj);
-                        overloadedMethods.addMethod(method);
-                        map.put(name, overloadedMethods);
-                    } else if (obj instanceof OverloadedMethods) {
-                        OverloadedMethods overloadedMethods = (OverloadedMethods) obj;
-                        overloadedMethods.addMethod(method);
-                    } else {
-                        if (obj != null) {
-                            if (LOG.isInfoEnabled()) {
-                                LOG.info("Overwriting value [" + obj + "] for " +
-                                        " key '" + name + "' with [" + method +
-                                        "] in static model for " + clazz.getName());
-                            }
-                        }
-                        map.put(name, method);
-                    }
-                }
-            }
-            for (Iterator entries = map.entrySet().iterator(); entries.hasNext(); ) {
-                Map.Entry entry = (Map.Entry) entries.next();
-                Object value = entry.getValue();
-                if (value instanceof Method) {
-                    Method method = (Method) value;
-                    entry.setValue(new SimpleMethodModel(null, method, 
-                            method.getParameterTypes(), wrapper));
-                } else if (value instanceof OverloadedMethods) {
-                    entry.setValue(new OverloadedMethodsModel(null, (OverloadedMethods) value, wrapper));
-                }
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/StaticModels.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/StaticModels.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/StaticModels.java
deleted file mode 100644
index f89fda8..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/StaticModels.java
+++ /dev/null
@@ -1,43 +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.beans;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- * Utility class for instantiating {@link StaticModel} instances from
- * templates. If your template's data model contains an instance of
- * StaticModels (named, say <tt>StaticModels</tt>), then you can
- * instantiate an arbitrary StaticModel using get syntax (i.e.
- * <tt>StaticModels["java.lang.System"].currentTimeMillis()</tt>).
- */
-class StaticModels extends ClassBasedModelFactory {
-    
-    StaticModels(BeansWrapper wrapper) {
-        super(wrapper);
-    }
-
-    @Override
-    protected TemplateModel createModel(Class clazz) 
-    throws TemplateModelException {
-        return new StaticModel(clazz, getWrapper());
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/StringModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/StringModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/StringModel.java
deleted file mode 100644
index 0591f6e..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/StringModel.java
+++ /dev/null
@@ -1,63 +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.beans;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-
-/**
- * Subclass of {@link BeanModel} that exposes the return value of the {@link
- * java.lang.Object#toString()} method through the {@link TemplateScalarModel}
- * interface.
- */
-public class StringModel extends BeanModel
-implements TemplateScalarModel {
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new StringModel(object, (BeansWrapper) wrapper);
-            }
-        };
-
-    /**
-     * Creates a new model that wraps the specified object with BeanModel + scalar
-     * functionality.
-     * @param object the object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     */
-    public StringModel(Object object, BeansWrapper wrapper) {
-        super(object, wrapper);
-    }
-
-    /**
-     * Returns the result of calling {@link Object#toString()} on the wrapped
-     * object.
-     */
-    @Override
-    public String getAsString() {
-        return object.toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/TypeFlags.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/TypeFlags.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/TypeFlags.java
deleted file mode 100644
index e836e47..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/TypeFlags.java
+++ /dev/null
@@ -1,130 +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.beans;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Flag values and masks for "type flags". "Type flags" is a set of bits that store information about the possible
- * destination types at a parameter position of overloaded methods. 
- */
-class TypeFlags {
-
-    /**
-     * Indicates that the unwrapping hint will not be a specific numerical type; it must not be set if there's no
-     * numerical type at the given parameter index.
-     */
-    static final int WIDENED_NUMERICAL_UNWRAPPING_HINT = 1;
-    
-    static final int BYTE = 4;
-    static final int SHORT = 8;
-    static final int INTEGER = 16;
-    static final int LONG = 32;
-    static final int FLOAT = 64;
-    static final int DOUBLE = 128;
-    static final int BIG_INTEGER = 256;
-    static final int BIG_DECIMAL = 512;
-    static final int UNKNOWN_NUMERICAL_TYPE = 1024;
-
-    static final int ACCEPTS_NUMBER = 0x800;
-    static final int ACCEPTS_DATE = 0x1000;
-    static final int ACCEPTS_STRING = 0x2000;
-    static final int ACCEPTS_BOOLEAN = 0x4000;
-    static final int ACCEPTS_MAP = 0x8000;
-    static final int ACCEPTS_LIST = 0x10000;
-    static final int ACCEPTS_SET = 0x20000;
-    static final int ACCEPTS_ARRAY = 0x40000;
-    
-    /**
-     * Indicates the presence of the char or Character type
-     */
-    static final int CHARACTER = 0x80000;
-    
-    static final int ACCEPTS_ANY_OBJECT = ACCEPTS_NUMBER | ACCEPTS_DATE | ACCEPTS_STRING | ACCEPTS_BOOLEAN
-            | ACCEPTS_MAP | ACCEPTS_LIST | ACCEPTS_SET | ACCEPTS_ARRAY;
-    
-    static final int MASK_KNOWN_INTEGERS = BYTE | SHORT | INTEGER | LONG | BIG_INTEGER;
-    static final int MASK_KNOWN_NONINTEGERS = FLOAT | DOUBLE | BIG_DECIMAL;
-    static final int MASK_ALL_KNOWN_NUMERICALS = MASK_KNOWN_INTEGERS | MASK_KNOWN_NONINTEGERS;
-    static final int MASK_ALL_NUMERICALS = MASK_ALL_KNOWN_NUMERICALS | UNKNOWN_NUMERICAL_TYPE;
-    
-    static int classToTypeFlags(Class pClass) {
-        // We start with the most frequent cases  
-        if (pClass == Object.class) {
-            return ACCEPTS_ANY_OBJECT;
-        } else if (pClass == String.class) {
-            return ACCEPTS_STRING;
-        } else if (pClass.isPrimitive()) {
-            if (pClass == Integer.TYPE) return INTEGER | ACCEPTS_NUMBER;
-            else if (pClass == Long.TYPE) return LONG | ACCEPTS_NUMBER;
-            else if (pClass == Double.TYPE) return DOUBLE | ACCEPTS_NUMBER;
-            else if (pClass == Float.TYPE) return FLOAT | ACCEPTS_NUMBER;
-            else if (pClass == Byte.TYPE) return BYTE | ACCEPTS_NUMBER;
-            else if (pClass == Short.TYPE) return SHORT | ACCEPTS_NUMBER;
-            else if (pClass == Character.TYPE) return CHARACTER;
-            else if (pClass == Boolean.TYPE) return ACCEPTS_BOOLEAN;
-            else return 0;
-        } else if (Number.class.isAssignableFrom(pClass)) {
-            if (pClass == Integer.class) return INTEGER | ACCEPTS_NUMBER;
-            else if (pClass == Long.class) return LONG | ACCEPTS_NUMBER;
-            else if (pClass == Double.class) return DOUBLE | ACCEPTS_NUMBER;
-            else if (pClass == Float.class) return FLOAT | ACCEPTS_NUMBER;
-            else if (pClass == Byte.class) return BYTE | ACCEPTS_NUMBER;
-            else if (pClass == Short.class) return SHORT | ACCEPTS_NUMBER;
-            else if (BigDecimal.class.isAssignableFrom(pClass)) return BIG_DECIMAL | ACCEPTS_NUMBER;
-            else if (BigInteger.class.isAssignableFrom(pClass)) return BIG_INTEGER | ACCEPTS_NUMBER;
-            else return UNKNOWN_NUMERICAL_TYPE | ACCEPTS_NUMBER;
-        } else if (pClass.isArray()) {
-            return ACCEPTS_ARRAY;
-        } else {
-            int flags = 0;
-            if (pClass.isAssignableFrom(String.class)) {
-                flags |= ACCEPTS_STRING;
-            }
-            if (pClass.isAssignableFrom(Date.class)) {
-                flags |= ACCEPTS_DATE;
-            }
-            if (pClass.isAssignableFrom(Boolean.class)) {
-                flags |= ACCEPTS_BOOLEAN;
-            }
-            if (pClass.isAssignableFrom(Map.class)) {
-                flags |= ACCEPTS_MAP;
-            }
-            if (pClass.isAssignableFrom(List.class)) {
-                flags |= ACCEPTS_LIST;
-            }
-            if (pClass.isAssignableFrom(Set.class)) {
-                flags |= ACCEPTS_SET;
-            }
-            
-            if (pClass == Character.class) {
-                flags |= CHARACTER;
-            }
-            
-            return flags;
-        } 
-    }
-
-}

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/_BeansAPI.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/_BeansAPI.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/_BeansAPI.java
deleted file mode 100644
index cac3ba7..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/_BeansAPI.java
+++ /dev/null
@@ -1,220 +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.beans;
-
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Array;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util._CollectionUtil;
-
-/**
- * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
- * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can
- * access things inside this package that users shouldn't. 
- */ 
-public class _BeansAPI {
-
-    private _BeansAPI() { }
-    
-    public static Object newInstance(Class<?> pClass, Object[] args, BeansWrapper bw)
-            throws NoSuchMethodException, IllegalArgumentException, InstantiationException,
-            IllegalAccessException, InvocationTargetException, TemplateModelException {
-        return newInstance(getConstructorDescriptor(pClass, args), args, bw);
-    }
-    
-    /**
-     * Gets the constructor that matches the types of the arguments the best. So this is more
-     * than what the Java reflection API provides in that it can handle overloaded constructors. This re-uses the
-     * overloaded method selection logic of {@link BeansWrapper}.
-     */
-    private static CallableMemberDescriptor getConstructorDescriptor(Class<?> pClass, Object[] args)
-            throws NoSuchMethodException {
-        if (args == null) args = _CollectionUtil.EMPTY_OBJECT_ARRAY;
-        
-        final ArgumentTypes argTypes = new ArgumentTypes(args);
-        final List<ReflectionCallableMemberDescriptor> fixedArgMemberDescs
-                = new ArrayList<>();
-        final List<ReflectionCallableMemberDescriptor> varArgsMemberDescs
-                = new ArrayList<>();
-        for (Constructor<?> constr : pClass.getConstructors()) {
-            ReflectionCallableMemberDescriptor memberDesc = new ReflectionCallableMemberDescriptor(constr, constr.getParameterTypes());
-            if (!_MethodUtil.isVarargs(constr)) {
-                fixedArgMemberDescs.add(memberDesc);
-            } else {
-                varArgsMemberDescs.add(memberDesc);
-            }
-        }
-        
-        MaybeEmptyCallableMemberDescriptor contrDesc = argTypes.getMostSpecific(fixedArgMemberDescs, false);
-        if (contrDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) {
-            contrDesc = argTypes.getMostSpecific(varArgsMemberDescs, true);
-        }
-        
-        if (contrDesc instanceof EmptyCallableMemberDescriptor) {
-            if (contrDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) {
-                throw new NoSuchMethodException(
-                        "There's no public " + pClass.getName()
-                        + " constructor with compatible parameter list.");
-            } else if (contrDesc == EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD) {
-                throw new NoSuchMethodException(
-                        "There are multiple public " + pClass.getName()
-                        + " constructors that match the compatible parameter list with the same preferability.");
-            } else {
-                throw new NoSuchMethodException();
-            }
-        } else {
-            return (CallableMemberDescriptor) contrDesc;
-        }
-    }
-    
-    private static Object newInstance(CallableMemberDescriptor constrDesc, Object[] args, BeansWrapper bw)
-            throws InstantiationException, IllegalAccessException, InvocationTargetException, IllegalArgumentException,
-            TemplateModelException {
-        if (args == null) args = _CollectionUtil.EMPTY_OBJECT_ARRAY;
-        
-        final Object[] packedArgs;
-        if (constrDesc.isVarargs()) {
-            // We have to put all the varargs arguments into a single array argument.
-
-            final Class<?>[] paramTypes = constrDesc.getParamTypes();
-            final int fixedArgCnt = paramTypes.length - 1;
-            
-            packedArgs = new Object[fixedArgCnt + 1]; 
-            for (int i = 0; i < fixedArgCnt; i++) {
-                packedArgs[i] = args[i];
-            }
-            
-            final Class<?> compType = paramTypes[fixedArgCnt].getComponentType();
-            final int varArgCnt = args.length - fixedArgCnt;
-            final Object varArgsArray = Array.newInstance(compType, varArgCnt);
-            for (int i = 0; i < varArgCnt; i++) {
-                Array.set(varArgsArray, i, args[fixedArgCnt + i]);
-            }
-            packedArgs[fixedArgCnt] = varArgsArray;
-        } else {
-            packedArgs = args;
-        }
-        
-        return constrDesc.invokeConstructor(bw, packedArgs);
-    }
-    
-    /**
-     * Contains the common parts of the singleton management for {@link BeansWrapper} and {@link DefaultObjectWrapper}.  
-     *  
-     * @param beansWrapperSubclassFactory Creates a <em>new</em> read-only object wrapper of the desired
-     *     {@link BeansWrapper} subclass. 
-     */
-    public static <BW extends BeansWrapper, BWC extends BeansWrapperConfiguration> BW getBeansWrapperSubclassSingleton(
-            BWC settings,
-            Map<ClassLoader, Map<BWC, WeakReference<BW>>> instanceCache,
-            ReferenceQueue<BW> instanceCacheRefQue,
-            _BeansWrapperSubclassFactory<BW, BWC> beansWrapperSubclassFactory) {
-        // BeansWrapper 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<BW> instanceRef;
-        Map<BWC, WeakReference<BW>> tcclScopedCache;
-        synchronized (instanceCache) {
-            tcclScopedCache = instanceCache.get(tccl);
-            if (tcclScopedCache == null) {
-                tcclScopedCache = new HashMap<>();
-                instanceCache.put(tccl, tcclScopedCache);
-                instanceRef = null;
-            } else {
-                instanceRef = tcclScopedCache.get(settings);
-            }
-        }
-
-        BW instance = instanceRef != null ? instanceRef.get() : null;
-        if (instance != null) {  // cache hit
-            return instance;
-        }
-        // cache miss
-        
-        settings = clone(settings);  // prevent any aliasing issues 
-        instance = beansWrapperSubclassFactory.create(settings);
-        if (!instance.isWriteProtected()) {
-            throw new BugException();
-        }
-        
-        synchronized (instanceCache) {
-            instanceRef = tcclScopedCache.get(settings);
-            BW concurrentInstance = instanceRef != null ? instanceRef.get() : null;
-            if (concurrentInstance == null) {
-                tcclScopedCache.put(settings, new WeakReference<>(instance, instanceCacheRefQue));
-            } else {
-                instance = concurrentInstance;
-            }
-        }
-        
-        removeClearedReferencesFromCache(instanceCache, instanceCacheRefQue);
-        
-        return instance;
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <BWC extends BeansWrapperConfiguration> BWC clone(BWC settings) {
-        return (BWC) settings.clone(true);
-    }
-    
-    private static <BW extends BeansWrapper, BWC extends BeansWrapperConfiguration>
-            void removeClearedReferencesFromCache(
-                    Map<ClassLoader, Map<BWC, WeakReference<BW>>> instanceCache,
-                    ReferenceQueue<BW> instanceCacheRefQue) {
-        Reference<? extends BW> 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(); ) {
-                        if (it2.next() == clearedRef) {
-                            it2.remove();
-                            break findClearedRef;
-                        }
-                    }
-                }
-            } // sync
-        } // while poll
-    }
-    
-    /**
-     * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
-     */
-    public interface _BeansWrapperSubclassFactory<BW extends BeansWrapper, BWC extends BeansWrapperConfiguration> {
-        
-        /** Creates a new read-only {@link BeansWrapper}; used for {@link BeansWrapperBuilder} and such. */
-        BW create(BWC sa);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/_EnumModels.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/_EnumModels.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/_EnumModels.java
deleted file mode 100644
index 573757b..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/_EnumModels.java
+++ /dev/null
@@ -1,54 +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.beans;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import org.apache.freemarker.core.model.TemplateModel;
-
-/**
- * Don't use this class; it's only public to work around Google App Engine Java
- * compliance issues. FreeMarker developers only: treat this class as package-visible.
- */
-public class _EnumModels extends ClassBasedModelFactory {
-
-    public _EnumModels(BeansWrapper wrapper) {
-        super(wrapper);
-    }
-    
-    @Override
-    protected TemplateModel createModel(Class clazz) {
-        Object[] obj = clazz.getEnumConstants();
-        if (obj == null) {
-            // Return null - it'll manifest itself as undefined in the template.
-            // We're doing this rather than throw an exception as this way 
-            // people can use someEnumModel?default({}) to gracefully fall back 
-            // to an empty hash if they want to.
-            return null;
-        }
-        Map map = new LinkedHashMap();
-        for (Object anObj : obj) {
-            Enum value = (Enum) anObj;
-            map.put(value.name(), value);
-        }
-        return new SimpleMapModel(map, getWrapper());
-    }
-}


[15/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..a34c390
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Array;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util.BugException;
+
+/**
+ * Stores the varargs methods for a {@link OverloadedMethods} object.
+ */
+class OverloadedVarArgsMethods extends OverloadedMethodsSubset {
+
+    OverloadedVarArgsMethods() {
+        super();
+    }
+    
+    /**
+     * Replaces the last parameter type with the array component type of it.
+     */
+    @Override
+    Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) {
+        final Class[] preprocessedParamTypes = memberDesc.getParamTypes().clone();
+        int ln = preprocessedParamTypes.length;
+        final Class varArgsCompType = preprocessedParamTypes[ln - 1].getComponentType();
+        if (varArgsCompType == null) {
+            throw new BugException("Only varargs methods should be handled here");
+        }
+        preprocessedParamTypes[ln - 1] = varArgsCompType;
+        return preprocessedParamTypes;
+    }
+
+    @Override
+    void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) {
+        // Overview
+        // --------
+        //
+        // So far, m(t1, t2...) was treated by the hint widening like m(t1, t2). So now we have to continue hint
+        // widening like if we had further methods:
+        // - m(t1, t2, t2), m(t1, t2, t2, t2), ...
+        // - 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.
+        // So we only update the already existing hints. Remember that we already have m(t1, t2) there.
+        
+        final int paramCount = paramTypes.length;
+        final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();
+        
+        // The case of e(t1, t2), e(t1, t2, t2), e(t1, t2, t2, t2), ..., where e is an *earlierly* added method.
+        // When that was added, this method wasn't added yet, so it had no chance updating the hints of this method,
+        // so we do that now:
+        // FIXME: Only needed if m(t1, t2) was filled an empty slot, otherwise whatever was there was already
+        // widened by the preceding hints, so this will be a no-op.
+        for (int i = paramCount - 1; i >= 0; i--) {
+            final Class[] previousHints = unwrappingHintsByParamCount[i];
+            if (previousHints != null) {
+                widenHintsToCommonSupertypes(
+                        paramCount,
+                        previousHints, getTypeFlags(i));
+                break;  // we only do this for the first hit, as the methods before that has already widened it.
+            }
+        }
+        // The case of e(t1), where e is an *earlier* added method.
+        // When that was added, this method wasn't added yet, so it had no chance updating the hints of this method,
+        // so we do that now:
+        // FIXME: Same as above; it's often unnecessary.
+        if (paramCount + 1 < unwrappingHintsByParamCount.length) {
+            Class[] oneLongerHints = unwrappingHintsByParamCount[paramCount + 1];
+            if (oneLongerHints != null) {
+                widenHintsToCommonSupertypes(
+                        paramCount,
+                        oneLongerHints, getTypeFlags(paramCount + 1));
+            }
+        }
+        
+        // The case of m(t1, t2, t2), m(t1, t2, t2, t2), ..., where m is the currently added method.
+        // Update the longer hints-arrays:  
+        for (int i = paramCount + 1; i < unwrappingHintsByParamCount.length; i++) {
+            widenHintsToCommonSupertypes(
+                    i,
+                    paramTypes, paramNumericalTypes);
+        }
+        // The case of m(t1) where m is the currently added method.
+        // update the one-shorter hints-array:  
+        if (paramCount > 0) {  // (should be always true, or else it wasn't a varags method)
+            widenHintsToCommonSupertypes(
+                    paramCount - 1,
+                    paramTypes, paramNumericalTypes);
+        }
+        
+    }
+    
+    private void widenHintsToCommonSupertypes(
+            int paramCountOfWidened, Class[] wideningTypes, int[] wideningTypeFlags) {
+        final Class[] typesToWiden = getUnwrappingHintsByParamCount()[paramCountOfWidened];
+        if (typesToWiden == null) { 
+            return;  // no such overload exists; nothing to widen
+        }
+        
+        final int typesToWidenLen = typesToWiden.length;
+        final int wideningTypesLen = wideningTypes.length;
+        int min = Math.min(wideningTypesLen, typesToWidenLen);
+        for (int i = 0; i < min; ++i) {
+            typesToWiden[i] = getCommonSupertypeForUnwrappingHint(typesToWiden[i], wideningTypes[i]);
+        }
+        if (typesToWidenLen > wideningTypesLen) {
+            Class varargsComponentType = wideningTypes[wideningTypesLen - 1];
+            for (int i = wideningTypesLen; i < typesToWidenLen; ++i) {
+                typesToWiden[i] = getCommonSupertypeForUnwrappingHint(typesToWiden[i], varargsComponentType);
+            }
+        }
+        
+        mergeInTypesFlags(paramCountOfWidened, wideningTypeFlags);
+    }
+    
+    @Override
+    MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, DefaultObjectWrapper unwrapper) 
+    throws TemplateModelException {
+        if (tmArgs == null) {
+            // null is treated as empty args
+            tmArgs = Collections.EMPTY_LIST;
+        }
+        final int argsLen = tmArgs.size();
+        final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();
+        final Object[] pojoArgs = new Object[argsLen];
+        int[] typesFlags = null;
+        // Going down starting from methods with args.length + 1 parameters, because we must try to match against a case
+        // where all specified args are fixargs, and we have 0 varargs.
+        outer: for (int paramCount = Math.min(argsLen + 1, unwrappingHintsByParamCount.length - 1); paramCount >= 0; --paramCount) {
+            Class[] unwarappingHints = unwrappingHintsByParamCount[paramCount];
+            if (unwarappingHints == null) {
+                if (paramCount == 0) {
+                    return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS;
+                }
+                continue;
+            }
+            
+            typesFlags = getTypeFlags(paramCount);
+            if (typesFlags == ALL_ZEROS_ARRAY) {
+                typesFlags = null;
+            }
+            
+            // Try to unwrap the arguments
+            Iterator it = tmArgs.iterator();
+            for (int i = 0; i < argsLen; ++i) {
+                int paramIdx = i < paramCount ? i : paramCount - 1;
+                Object pojo = unwrapper.tryUnwrapTo(
+                        (TemplateModel) it.next(),
+                        unwarappingHints[paramIdx],
+                        typesFlags != null ? typesFlags[paramIdx] : 0);
+                if (pojo == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+                    continue outer;
+                }
+                pojoArgs[i] = pojo;
+            }
+            break outer;
+        }
+        
+        MaybeEmptyCallableMemberDescriptor maybeEmtpyMemberDesc = getMemberDescriptorForArgs(pojoArgs, true);
+        if (maybeEmtpyMemberDesc instanceof CallableMemberDescriptor) {
+            CallableMemberDescriptor memberDesc = (CallableMemberDescriptor) maybeEmtpyMemberDesc;
+            Object[] pojoArgsWithArray;
+            Object argsOrErrorIdx = replaceVarargsSectionWithArray(pojoArgs, tmArgs, memberDesc, unwrapper);
+            if (argsOrErrorIdx instanceof Object[]) {
+                pojoArgsWithArray = (Object[]) argsOrErrorIdx;
+            } else {
+                return EmptyMemberAndArguments.noCompatibleOverload(((Integer) argsOrErrorIdx).intValue());
+            }
+            if (typesFlags != null) {
+                // Note that overloaded method selection has already accounted for overflow errors when the method
+                // was selected. So this forced conversion shouldn't cause such corruption. Except, conversion from
+                // BigDecimal is allowed to overflow for backward-compatibility.
+                forceNumberArgumentsToParameterTypes(pojoArgsWithArray, memberDesc.getParamTypes(), typesFlags);
+            }
+            return new MemberAndArguments(memberDesc, pojoArgsWithArray);
+        } else {
+            return EmptyMemberAndArguments.from((EmptyCallableMemberDescriptor) maybeEmtpyMemberDesc, pojoArgs);
+        }
+    }
+    
+    /**
+     * Converts a flat argument list to one where the last argument is an array that collects the varargs, also
+     * re-unwraps the varargs to the component type. Note that this couldn't be done until we had the concrete
+     * member selected.
+     * 
+     * @return An {@code Object[]} if everything went well, or an {@code Integer} the
+     *    order (1-based index) of the argument that couldn't be unwrapped. 
+     */
+    private Object replaceVarargsSectionWithArray(
+            Object[] args, List modelArgs, CallableMemberDescriptor memberDesc, DefaultObjectWrapper unwrapper) 
+    throws TemplateModelException {
+        final Class[] paramTypes = memberDesc.getParamTypes();
+        final int paramCount = paramTypes.length;
+        final Class varArgsCompType = paramTypes[paramCount - 1].getComponentType(); 
+        final int totalArgCount = args.length;
+        final int fixArgCount = paramCount - 1;
+        if (args.length != paramCount) {
+            Object[] packedArgs = new Object[paramCount];
+            System.arraycopy(args, 0, packedArgs, 0, fixArgCount);
+            Object varargs = Array.newInstance(varArgsCompType, totalArgCount - fixArgCount);
+            for (int i = fixArgCount; i < totalArgCount; ++i) {
+                Object val = unwrapper.tryUnwrapTo((TemplateModel) modelArgs.get(i), varArgsCompType);
+                if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+                    return Integer.valueOf(i + 1);
+                }
+                Array.set(varargs, i - fixArgCount, val);
+            }
+            packedArgs[fixArgCount] = varargs;
+            return packedArgs;
+        } else {
+            Object val = unwrapper.tryUnwrapTo((TemplateModel) modelArgs.get(fixArgCount), varArgsCompType);
+            if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+                return Integer.valueOf(fixArgCount + 1);
+            }
+            Object array = Array.newInstance(varArgsCompType, 1);
+            Array.set(array, 0, val);
+            args[fixArgCount] = array;
+            return args;
+        }
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/PrimtiveArrayBackedReadOnlyList.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/PrimtiveArrayBackedReadOnlyList.java b/src/main/java/org/apache/freemarker/core/model/impl/PrimtiveArrayBackedReadOnlyList.java
new file mode 100644
index 0000000..46e1cdf
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/PrimtiveArrayBackedReadOnlyList.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Array;
+import java.util.AbstractList;
+
+/**
+ * Similar to {@link NonPrimitiveArrayBackedReadOnlyList}, but uses reflection so that it works with primitive arrays
+ * too. 
+ */
+class PrimtiveArrayBackedReadOnlyList extends AbstractList {
+    
+    private final Object array;
+    
+    PrimtiveArrayBackedReadOnlyList(Object array) {
+        this.array = array;
+    }
+
+    @Override
+    public Object get(int index) {
+        return Array.get(array, index);
+    }
+
+    @Override
+    public int size() {
+        return Array.getLength(array);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java
new file mode 100644
index 0000000..61eb87c
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * The most commonly used {@link CallableMemberDescriptor} implementation. 
+ */
+final class ReflectionCallableMemberDescriptor extends CallableMemberDescriptor {
+
+    private final Member/*Method|Constructor*/ member;
+    
+    /**
+     * Don't modify this array!
+     */
+    final Class[] paramTypes;
+    
+    ReflectionCallableMemberDescriptor(Method member, Class[] paramTypes) {
+        this.member = member;
+        this.paramTypes = paramTypes;
+    }
+
+    ReflectionCallableMemberDescriptor(Constructor member, Class[] paramTypes) {
+        this.member = member;
+        this.paramTypes = paramTypes;
+    }
+
+    @Override
+    TemplateModel invokeMethod(DefaultObjectWrapper ow, Object obj, Object[] args)
+            throws TemplateModelException, InvocationTargetException, IllegalAccessException {
+        return ow.invokeMethod(obj, (Method) member, args);
+    }
+
+    @Override
+    Object invokeConstructor(DefaultObjectWrapper ow, Object[] args)
+            throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
+        return ((Constructor) member).newInstance(args);
+    }
+
+    @Override
+    String getDeclaration() {
+        return _MethodUtil.toString(member);
+    }
+    
+    @Override
+    boolean isConstructor() {
+        return member instanceof Constructor;
+    }
+    
+    @Override
+    boolean isStatic() {
+        return (member.getModifiers() & Modifier.STATIC) != 0;
+    }
+
+    @Override
+    boolean isVarargs() {
+        return _MethodUtil.isVarargs(member);
+    }
+
+    @Override
+    Class[] getParamTypes() {
+        return paramTypes;
+    }
+
+    @Override
+    String getName() {
+        return member.getName();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java b/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
new file mode 100644
index 0000000..27bdf96
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
@@ -0,0 +1,190 @@
+/*
+ * 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.text.MessageFormat;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import org.apache.freemarker.core._DelayedJQuote;
+import org.apache.freemarker.core._TemplateModelException;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * <p>A hash model that wraps a resource bundle. Makes it convenient to store
+ * localized content in the data model. It also acts as a method model that will
+ * take a resource key and arbitrary number of arguments and will apply
+ * {@link MessageFormat} with arguments on the string represented by the key.</p>
+ *
+ * <p>Typical usages:</p>
+ * <ul>
+ * <li><tt>bundle.resourceKey</tt> will retrieve the object from resource bundle
+ * with key <tt>resourceKey</tt></li>
+ * <li><tt>bundle("patternKey", arg1, arg2, arg3)</tt> will retrieve the string
+ * from resource bundle with key <tt>patternKey</tt>, and will use it as a pattern
+ * for MessageFormat with arguments arg1, arg2 and arg3</li>
+ * </ul>
+ */
+public class ResourceBundleModel
+    extends
+    BeanModel
+    implements
+    TemplateMethodModelEx {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new ResourceBundleModel((ResourceBundle) object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+
+    private Hashtable formats = null;
+
+    public ResourceBundleModel(ResourceBundle bundle, DefaultObjectWrapper wrapper) {
+        super(bundle, wrapper);
+    }
+
+    /**
+     * Overridden to invoke the getObject method of the resource bundle.
+     */
+    @Override
+    protected TemplateModel invokeGenericGet(Map keyMap, Class clazz, String key)
+    throws TemplateModelException {
+        try {
+            return wrap(((ResourceBundle) object).getObject(key));
+        } catch (MissingResourceException e) {
+            throw new _TemplateModelException(e,
+                    "No ", new _DelayedJQuote(key), " key in the ResourceBundle. "
+                    + "Note that conforming to the ResourceBundle Java API, this is an error and not just "
+                    + "a missing sub-variable (a null).");
+        }
+    }
+
+    /**
+     * Returns true if this bundle contains no objects.
+     */
+    @Override
+    public boolean isEmpty() {
+        return !((ResourceBundle) object).getKeys().hasMoreElements() &&
+            super.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return keySet().size();
+    }
+
+    @Override
+    protected Set keySet() {
+        Set set = super.keySet();
+        Enumeration e = ((ResourceBundle) object).getKeys();
+        while (e.hasMoreElements()) {
+            set.add(e.nextElement());
+        }
+        return set;
+    }
+
+    /**
+     * Takes first argument as a resource key, looks up a string in resource bundle
+     * with this key, then applies a MessageFormat.format on the string with the
+     * rest of the arguments. The created MessageFormats are cached for later reuse.
+     */
+    @Override
+    public Object exec(List arguments)
+        throws TemplateModelException {
+        // Must have at least one argument - the key
+        if (arguments.size() < 1)
+            throw new TemplateModelException("No message key was specified");
+        // Read it
+        Iterator it = arguments.iterator();
+        String key = unwrap((TemplateModel) it.next()).toString();
+        try {
+            if (!it.hasNext()) {
+                return wrap(((ResourceBundle) object).getObject(key));
+            }
+    
+            // Copy remaining arguments into an Object[]
+            int args = arguments.size() - 1;
+            Object[] params = new Object[args];
+            for (int i = 0; i < args; ++i)
+                params[i] = unwrap((TemplateModel) it.next());
+    
+            // Invoke format
+            return new StringModel(format(key, params), wrapper);
+        } catch (MissingResourceException e) {
+            throw new TemplateModelException("No such key: " + key);
+        } catch (Exception e) {
+            throw new TemplateModelException(e.getMessage());
+        }
+    }
+
+    /**
+     * Provides direct access to caching format engine from code (instead of from script).
+     */
+    public String format(String key, Object[] params)
+        throws MissingResourceException {
+        // Check to see if we already have a cache for message formats
+        // and construct it if we don't
+        // NOTE: this block statement should be synchronized. However
+        // concurrent creation of two caches will have no harmful
+        // consequences, and we avoid a performance hit.
+        /* synchronized(this) */
+        {
+            if (formats == null)
+                formats = new Hashtable();
+        }
+
+        MessageFormat format = null;
+        // Check to see if we already have a requested MessageFormat cached
+        // and construct it if we don't
+        // NOTE: this block statement should be synchronized. However
+        // concurrent creation of two formats will have no harmful
+        // consequences, and we avoid a performance hit.
+        /* synchronized(formats) */
+        {
+            format = (MessageFormat) formats.get(key);
+            if (format == null) {
+                format = new MessageFormat(((ResourceBundle) object).getString(key));
+                format.setLocale(getBundle().getLocale());
+                formats.put(key, format);
+            }
+        }
+
+        // Perform the formatting. We synchronize on it in case it
+        // contains date formatting, which is not thread-safe.
+        synchronized (format) {
+            return format.format(params);
+        }
+    }
+
+    public ResourceBundle getBundle() {
+        return (ResourceBundle) object;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java
new file mode 100644
index 0000000..96da1b8
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java
@@ -0,0 +1,68 @@
+/*
+ * 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.util.AbstractList;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ */
+class SequenceAdapter extends AbstractList implements TemplateModelAdapter {
+    private final DefaultObjectWrapper wrapper;
+    private final TemplateSequenceModel model;
+    
+    SequenceAdapter(TemplateSequenceModel model, DefaultObjectWrapper wrapper) {
+        this.model = model;
+        this.wrapper = wrapper;
+    }
+    
+    @Override
+    public TemplateModel getTemplateModel() {
+        return model;
+    }
+    
+    @Override
+    public int size() {
+        try {
+            return model.size();
+        } catch (TemplateModelException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+    
+    @Override
+    public Object get(int index) {
+        try {
+            return wrapper.unwrap(model.get(index));
+        } catch (TemplateModelException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+    
+    public TemplateSequenceModel getTemplateSequenceModel() {
+        return model;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java
new file mode 100644
index 0000000..a589040
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util.Set;
+
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+
+/**
+ */
+class SetAdapter extends CollectionAdapter implements Set {
+    SetAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) {
+        super(model, wrapper);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 40c33f7..fa44144 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
@@ -37,7 +37,6 @@ import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.WrappingTemplateModel;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
 
 /**
  * A simple implementation of the {@link TemplateHashModelEx} interface, using its own underlying {@link Map} or
@@ -308,13 +307,13 @@ public class SimpleHash extends WrappingTemplateModel implements TemplateHashMod
             }
             // Create a copy to maintain immutability semantics and
             // Do nested unwrapping of elements if necessary.
-            BeansWrapper bw = _StaticObjectWrappers.BEANS_WRAPPER;
+            DefaultObjectWrapper ow = _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER;
             for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
                 Map.Entry entry = (Map.Entry) it.next();
                 Object key = entry.getKey();
                 Object value = entry.getValue();
                 if (value instanceof TemplateModel) {
-                    value = bw.unwrap((TemplateModel) value);
+                    value = ow.unwrap((TemplateModel) value);
                 }
                 m.put(key, value);
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java
new file mode 100644
index 0000000..cf9836e
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java
@@ -0,0 +1,129 @@
+/*
+ * 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.util.List;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.AdapterTemplateModel;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.RichObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
+import org.apache.freemarker.core.model.WrapperTemplateModel;
+import org.apache.freemarker.core.model.WrappingTemplateModel;
+
+/**
+ * Model used by {@link DefaultObjectWrapper} when <tt>simpleMapWrapper</tt>
+ * mode is enabled. Provides a simple hash model interface to the
+ * underlying map (does not copy like {@link org.apache.freemarker.core.model.impl.SimpleHash}),
+ * and a method interface to non-string keys.
+ */
+public class SimpleMapModel extends WrappingTemplateModel 
+implements TemplateHashModelEx2, TemplateMethodModelEx, AdapterTemplateModel, 
+WrapperTemplateModel, TemplateModelWithAPISupport {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new SimpleMapModel((Map) object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+
+    private final Map map;
+    
+    public SimpleMapModel(Map map, DefaultObjectWrapper wrapper) {
+        super(wrapper);
+        this.map = map;
+    }
+
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        Object val = map.get(key);
+        if (val == null) {
+            if (key.length() == 1) {
+                // just check for Character key if this is a single-character string
+                Character charKey = Character.valueOf(key.charAt(0));
+                val = map.get(charKey);
+                if (val == null && !(map.containsKey(key) || map.containsKey(charKey))) {
+                    return null;
+                }
+            } else if (!map.containsKey(key)) {
+                return null;
+            }
+        }
+        return wrap(val);
+    }
+    
+    @Override
+    public Object exec(List args) throws TemplateModelException {
+        Object key = ((DefaultObjectWrapper) getObjectWrapper()).unwrap((TemplateModel) args.get(0));
+        Object value = map.get(key);
+        if (value == null && !map.containsKey(key)) {
+            return null;
+        }
+        return wrap(value);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public TemplateCollectionModel keys() {
+        return new CollectionAndSequence(new SimpleSequence(map.keySet(), getObjectWrapper()));
+    }
+
+    @Override
+    public TemplateCollectionModel values() {
+        return new CollectionAndSequence(new SimpleSequence(map.values(), getObjectWrapper()));
+    }
+    
+    @Override
+    public KeyValuePairIterator keyValuePairIterator() {
+        return new MapKeyValuePairIterator(map, getObjectWrapper());
+    }
+
+    @Override
+    public Object getAdaptedObject(Class hint) {
+        return map;
+    }
+    
+    @Override
+    public Object getWrappedObject() {
+        return map;
+    }
+
+    @Override
+    public TemplateModel getAPI() throws TemplateModelException {
+        return ((RichObjectWrapper) getObjectWrapper()).wrapAsAPI(map);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java
new file mode 100644
index 0000000..4fc81d0
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Member;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.freemarker.core._DelayedFTLTypeDescription;
+import org.apache.freemarker.core._DelayedOrdinal;
+import org.apache.freemarker.core._ErrorDescriptionBuilder;
+import org.apache.freemarker.core._TemplateModelException;
+import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._ClassUtil;
+
+/**
+ * This class is used for as a base for non-overloaded method models and for constructors.
+ * (For overloaded methods and constructors see {@link OverloadedMethods}.)
+ */
+class SimpleMethod {
+    
+    static final String MARKUP_OUTPUT_TO_STRING_TIP
+            = "A markup output value can be converted to markup string like value?markup_string. "
+              + "But consider if the Java method whose argument it will be can handle markup strings properly.";
+    
+    private final Member member;
+    private final Class[] argTypes;
+    
+    protected SimpleMethod(Member member, Class[] argTypes) {
+        this.member = member;
+        this.argTypes = argTypes;
+    }
+    
+    Object[] unwrapArguments(List arguments, DefaultObjectWrapper wrapper) throws TemplateModelException {
+        if (arguments == null) {
+            arguments = Collections.EMPTY_LIST;
+        }
+        boolean isVarArg = _MethodUtil.isVarargs(member);
+        int typesLen = argTypes.length;
+        if (isVarArg) {
+            if (typesLen - 1 > arguments.size()) {
+                throw new _TemplateModelException(
+                        _MethodUtil.invocationErrorMessageStart(member),
+                        " takes at least ", Integer.valueOf(typesLen - 1),
+                        typesLen - 1 == 1 ? " argument" : " arguments", ", but ",
+                        Integer.valueOf(arguments.size()), " was given.");
+            }
+        } else if (typesLen != arguments.size()) {
+            throw new _TemplateModelException(
+                    _MethodUtil.invocationErrorMessageStart(member), 
+                    " takes ", Integer.valueOf(typesLen), typesLen == 1 ? " argument" : " arguments", ", but ",
+                    Integer.valueOf(arguments.size()), " was given.");
+        }
+         
+        return unwrapArguments(arguments, argTypes, isVarArg, wrapper);
+    }
+
+    private Object[] unwrapArguments(List args, Class[] argTypes, boolean isVarargs,
+            DefaultObjectWrapper w)
+    throws TemplateModelException {
+        if (args == null) return null;
+        
+        int typesLen = argTypes.length;
+        int argsLen = args.size();
+        
+        Object[] unwrappedArgs = new Object[typesLen];
+        
+        // Unwrap arguments:
+        Iterator it = args.iterator();
+        int normalArgCnt = isVarargs ? typesLen - 1 : typesLen; 
+        int argIdx = 0;
+        while (argIdx < normalArgCnt) {
+            Class argType = argTypes[argIdx];
+            TemplateModel argVal = (TemplateModel) it.next();
+            Object unwrappedArgVal = w.tryUnwrapTo(argVal, argType);
+            if (unwrappedArgVal == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+                throw createArgumentTypeMismarchException(argIdx, argVal, argType);
+            }
+            if (unwrappedArgVal == null && argType.isPrimitive()) {
+                throw createNullToPrimitiveArgumentException(argIdx, argType); 
+            }
+            
+            unwrappedArgs[argIdx++] = unwrappedArgVal;
+        }
+        if (isVarargs) {
+            // The last argType, which is the vararg type, wasn't processed yet.
+            
+            Class varargType = argTypes[typesLen - 1];
+            Class varargItemType = varargType.getComponentType();
+            if (!it.hasNext()) {
+                unwrappedArgs[argIdx++] = Array.newInstance(varargItemType, 0);
+            } else {
+                TemplateModel argVal = (TemplateModel) it.next();
+                
+                Object unwrappedArgVal;
+                // We first try to treat the last argument as a vararg *array*.
+                // This is consistent to what OverloadedVarArgMethod does.
+                if (argsLen - argIdx == 1
+                        && (unwrappedArgVal = w.tryUnwrapTo(argVal, varargType))
+                            != ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+                    // It was a vararg array.
+                    unwrappedArgs[argIdx++] = unwrappedArgVal;
+                } else {
+                    // It wasn't a vararg array, so we assume it's a vararg
+                    // array *item*, possibly followed by further ones.
+                    int varargArrayLen = argsLen - argIdx;
+                    Object varargArray = Array.newInstance(varargItemType, varargArrayLen);
+                    for (int varargIdx = 0; varargIdx < varargArrayLen; varargIdx++) {
+                        TemplateModel varargVal = (TemplateModel) (varargIdx == 0 ? argVal : it.next());
+                        Object unwrappedVarargVal = w.tryUnwrapTo(varargVal, varargItemType);
+                        if (unwrappedVarargVal == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+                            throw createArgumentTypeMismarchException(
+                                    argIdx + varargIdx, varargVal, varargItemType);
+                        }
+                        
+                        if (unwrappedVarargVal == null && varargItemType.isPrimitive()) {
+                            throw createNullToPrimitiveArgumentException(argIdx + varargIdx, varargItemType); 
+                        }
+                        Array.set(varargArray, varargIdx, unwrappedVarargVal);
+                    }
+                    unwrappedArgs[argIdx++] = varargArray;
+                }
+            }
+        }
+        
+        return unwrappedArgs;
+    }
+
+    private TemplateModelException createArgumentTypeMismarchException(
+            int argIdx, TemplateModel argVal, Class targetType) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: Can't convert the ",
+                new _DelayedOrdinal(Integer.valueOf(argIdx + 1)),
+                " argument's value to the target Java type, ", _ClassUtil.getShortClassName(targetType),
+                ". The type of the actual value was: ", new _DelayedFTLTypeDescription(argVal));
+        if (argVal instanceof TemplateMarkupOutputModel && (targetType.isAssignableFrom(String.class))) {
+            desc.tip(MARKUP_OUTPUT_TO_STRING_TIP);
+        }
+        return new _TemplateModelException(desc);
+    }
+
+    private TemplateModelException createNullToPrimitiveArgumentException(int argIdx, Class targetType) {
+        return new _TemplateModelException(
+                _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: The value of the ",
+                new _DelayedOrdinal(Integer.valueOf(argIdx + 1)),
+                " argument was null, but the target Java parameter type (", _ClassUtil.getShortClassName(targetType),
+                ") is primitive and so can't store null.");
+    }
+    
+    protected Member getMember() {
+        return member;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethodModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethodModel.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethodModel.java
new file mode 100644
index 0000000..943d8f0
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethodModel.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.util.FTLUtil;
+
+/**
+ * A class that will wrap a reflected method call into a
+ * {@link org.apache.freemarker.core.model.TemplateMethodModel} interface. 
+ * It is used by {@link BeanModel} to wrap reflected method calls
+ * for non-overloaded methods.
+ */
+public final class SimpleMethodModel extends SimpleMethod
+    implements
+    TemplateMethodModelEx,
+    TemplateSequenceModel,
+    _UnexpectedTypeErrorExplainerTemplateModel {
+    private final Object object;
+    private final DefaultObjectWrapper wrapper;
+
+    /**
+     * Creates a model for a specific method on a specific object.
+     * @param object the object to call the method on, or {@code null} for a static method.
+     * @param method the method that will be invoked.
+     * @param argTypes Either pass in {@code Method#getParameterTypes() method.getParameterTypes()} here,
+     *          or reuse an earlier result of that call (for speed). Not {@code null}.
+     */
+    SimpleMethodModel(Object object, Method method, Class[] argTypes, 
+            DefaultObjectWrapper wrapper) {
+        super(method, argTypes);
+        this.object = object;
+        this.wrapper = wrapper;
+    }
+
+    /**
+     * Invokes the method, passing it the arguments from the list.
+     */
+    @Override
+    public Object exec(List arguments)
+        throws TemplateModelException {
+        try {
+            return wrapper.invokeMethod(object, (Method) getMember(), 
+                    unwrapArguments(arguments, wrapper));
+        } catch (TemplateModelException e) {
+            throw e;
+        } catch (Exception e) {
+            throw _MethodUtil.newInvocationTemplateModelException(object, getMember(), e);
+        }
+    }
+    
+    @Override
+    public TemplateModel get(int index) throws TemplateModelException {
+        return (TemplateModel) exec(Collections.singletonList(
+                new SimpleNumber(Integer.valueOf(index))));
+    }
+
+    @Override
+    public int size() throws TemplateModelException {
+        throw new TemplateModelException(
+                "Getting the number of items or enumerating the items is not supported on this "
+                + FTLUtil.getTypeDescription(this) + " value.\n"
+                + "("
+                + "Hint 1: Maybe you wanted to call this method first and then do something with its return value. "
+                + "Hint 2: Getting items by intex possibly works, hence it's a \"+sequence\"."
+                + ")");
+    }
+    
+    @Override
+    public String toString() {
+        return getMember().toString();
+    }
+
+    /**
+     * Implementation of experimental interface; don't use it, no backward compatibility guarantee!
+     */
+    @Override
+    public Object[] explainTypeError(Class[] expectedClasses) {
+        final Member member = getMember();
+        if (!(member instanceof Method)) {
+            return null;  // This shouldn't occur
+        }
+        Method m = (Method) member;
+        
+        final Class returnType = m.getReturnType();
+        if (returnType == null || returnType == void.class || returnType == Void.class) {
+            return null;  // Calling it won't help
+        }
+        
+        String mName = m.getName();
+        if (mName.startsWith("get") && mName.length() > 3 && Character.isUpperCase(mName.charAt(3))
+                && (m.getParameterTypes().length == 0)) {
+            return new Object[] {
+                    "Maybe using obj.something instead of obj.getSomething will yield the desired value." };
+        } else if (mName.startsWith("is") && mName.length() > 2 && Character.isUpperCase(mName.charAt(2))
+                && (m.getParameterTypes().length == 0)) {
+            return new Object[] {
+                    "Maybe using obj.something instead of obj.isSomething will yield the desired value." };
+        } else {
+            return new Object[] {
+                    "Maybe using obj.something(",
+                    (m.getParameterTypes().length != 0 ? "params" : ""),
+                    ") instead of obj.something will yield the desired value" };
+        }
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java
index 61378f0..281b8ac 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java
@@ -23,7 +23,7 @@ import org.apache.freemarker.core.Version;
 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.impl.beans.BeansWrapper;
+import org.w3c.dom.Node;
 
 /**
  * A restricted object wrapper that will not expose arbitrary object, just those that directly correspond to the
@@ -33,21 +33,30 @@ import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
 public class SimpleObjectWrapper extends DefaultObjectWrapper {
 
     /**
-     * @param incompatibleImprovements see in {@link BeansWrapper#BeansWrapper(Version)}.
+     * @param incompatibleImprovements see in {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}.
      * 
      * @since 2.3.21
      */
     public SimpleObjectWrapper(Version incompatibleImprovements) {
         super(incompatibleImprovements);
     }
-    
+
+    @Override
+    protected TemplateModel handW3CNode(Node node) throws TemplateModelException {
+        throw newUnhandledTypeException(node);
+    }
+
     /**
      * Called if a type other than the simple ones we know about is passed in. 
      * In this implementation, this just throws an exception.
      */
     @Override
     protected TemplateModel handleUnknownType(Object obj) throws TemplateModelException {
-        throw new TemplateModelException("SimpleObjectWrapper deliberately won't wrap this type: " 
+        throw newUnhandledTypeException(obj);
+    }
+
+    private TemplateModelException newUnhandledTypeException(Object obj) throws TemplateModelException {
+        return new TemplateModelException("SimpleObjectWrapper deliberately won't wrap this type: "
                                          + obj.getClass().getName());
     }
 
@@ -55,5 +64,5 @@ public class SimpleObjectWrapper extends DefaultObjectWrapper {
     public TemplateHashModel wrapAsAPI(Object obj) throws TemplateModelException {
         throw new TemplateModelException("SimpleObjectWrapper deliberately doesn't allow ?api.");
     }
-    
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 b0d5b81..fa59010 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
@@ -33,7 +33,6 @@ import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.WrappingTemplateModel;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
 
 /**
  * A simple implementation of the {@link TemplateSequenceModel} interface, using its own underlying {@link List} for
@@ -223,11 +222,11 @@ public class SimpleSequence extends WrappingTemplateModel implements TemplateSeq
                 throw new TemplateModelException("Error instantiating an object of type " + listClass.getName(),
                         e);
             }
-            BeansWrapper bw = _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER;
+            DefaultObjectWrapper ow = _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER;
             for (int i = 0; i < list.size(); i++) {
                 Object elem = list.get(i);
                 if (elem instanceof TemplateModel) {
-                    elem = bw.unwrap((TemplateModel) elem);
+                    elem = ow.unwrap((TemplateModel) elem);
                 }
                 result.add(elem);
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..70f50f6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.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,
+ * 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 {
+ *      
+ *    // This is the singleton:
+ *    static final MyMethodAppearanceFineTuner INSTANCE = new MyMethodAppearanceFineTuner();
+ *     
+ *    // Private, so it can't be constructed from outside this class.
+ *    private MyMethodAppearanceFineTuner() { }
+ *
+ *    &#64;Override
+ *    public void fineTuneMethodAppearance(...) {
+ *       // Do something here, only using the parameters and maybe some other singletons. 
+ *       ...
+ *    }
+ *     
+ * }</pre>
+ *
+ * @since 2.3.21
+ */
+public interface SingletonCustomizer {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java b/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
new file mode 100644
index 0000000..1a6263c
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.slf4j.Logger;
+
+/**
+ * Wraps the static fields and methods of a class in a
+ * {@link org.apache.freemarker.core.model.TemplateHashModel}.
+ * Fields are wrapped using {@link DefaultObjectWrapper#wrap(Object)}, and
+ * methods are wrapped into an appropriate {@link org.apache.freemarker.core.model.TemplateMethodModelEx} instance.
+ * Unfortunately, there is currently no support for bean property-style
+ * calls of static methods, similar to that in {@link BeanModel}.
+ */
+final class StaticModel implements TemplateHashModelEx {
+    
+    private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
+    
+    private final Class clazz;
+    private final DefaultObjectWrapper wrapper;
+    private final Map map = new HashMap();
+
+    StaticModel(Class clazz, DefaultObjectWrapper wrapper) throws TemplateModelException {
+        this.clazz = clazz;
+        this.wrapper = wrapper;
+        populate();
+    }
+
+    /**
+     * Returns the field or method named by the <tt>key</tt>
+     * parameter.
+     */
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        Object model = map.get(key);
+        // Simple method, overloaded method or final field -- these have cached 
+        // template models
+        if (model instanceof TemplateModel)
+            return (TemplateModel) model;
+        // Non-final field; this must be evaluated on each call.
+        if (model instanceof Field) {
+            try {
+                return wrapper.getOuterIdentity().wrap(((Field) model).get(null));
+            } catch (IllegalAccessException e) {
+                throw new TemplateModelException(
+                    "Illegal access for field " + key + " of class " + clazz.getName());
+            }
+        }
+
+        throw new TemplateModelException(
+            "No such key: " + key + " in class " + clazz.getName());
+    }
+
+    /**
+     * Returns true if there is at least one public static
+     * field or method in the underlying class.
+     */
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+    
+    @Override
+    public TemplateCollectionModel keys() throws TemplateModelException {
+        return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.keySet());
+    }
+    
+    @Override
+    public TemplateCollectionModel values() throws TemplateModelException {
+        return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.values());
+    }
+
+    private void populate() throws TemplateModelException {
+        if (!Modifier.isPublic(clazz.getModifiers())) {
+            throw new TemplateModelException(
+                "Can't wrap the non-public class " + clazz.getName());
+        }
+        
+        if (wrapper.getExposureLevel() == DefaultObjectWrapper.EXPOSE_NOTHING) {
+            return;
+        }
+
+        Field[] fields = clazz.getFields();
+        for (Field field : fields) {
+            int mod = field.getModifiers();
+            if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) {
+                if (Modifier.isFinal(mod))
+                    try {
+                        // public static final fields are evaluated once and
+                        // stored in the map
+                        map.put(field.getName(), wrapper.getOuterIdentity().wrap(field.get(null)));
+                    } catch (IllegalAccessException e) {
+                        // Intentionally ignored
+                    }
+                else
+                    // This is a special flagging value: Field in the map means
+                    // that this is a non-final field, and it must be evaluated
+                    // on each get() call.
+                    map.put(field.getName(), field);
+            }
+        }
+        if (wrapper.getExposureLevel() < DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY) {
+            Method[] methods = clazz.getMethods();
+            for (Method method : methods) {
+                int mod = method.getModifiers();
+                if (Modifier.isPublic(mod) && Modifier.isStatic(mod)
+                        && wrapper.getClassIntrospector().isAllowedToExpose(method)) {
+                    String name = method.getName();
+                    Object obj = map.get(name);
+                    if (obj instanceof Method) {
+                        OverloadedMethods overloadedMethods = new OverloadedMethods();
+                        overloadedMethods.addMethod((Method) obj);
+                        overloadedMethods.addMethod(method);
+                        map.put(name, overloadedMethods);
+                    } else if (obj instanceof OverloadedMethods) {
+                        OverloadedMethods overloadedMethods = (OverloadedMethods) obj;
+                        overloadedMethods.addMethod(method);
+                    } else {
+                        if (obj != null) {
+                            if (LOG.isInfoEnabled()) {
+                                LOG.info("Overwriting value [" + obj + "] for " +
+                                        " key '" + name + "' with [" + method +
+                                        "] in static model for " + clazz.getName());
+                            }
+                        }
+                        map.put(name, method);
+                    }
+                }
+            }
+            for (Iterator entries = map.entrySet().iterator(); entries.hasNext(); ) {
+                Map.Entry entry = (Map.Entry) entries.next();
+                Object value = entry.getValue();
+                if (value instanceof Method) {
+                    Method method = (Method) value;
+                    entry.setValue(new SimpleMethodModel(null, method, 
+                            method.getParameterTypes(), wrapper));
+                } else if (value instanceof OverloadedMethods) {
+                    entry.setValue(new OverloadedMethodsModel(null, (OverloadedMethods) value, wrapper));
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java b/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java
new file mode 100644
index 0000000..04dd3a5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java
@@ -0,0 +1,43 @@
+/*
+ * 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.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Utility class for instantiating {@link StaticModel} instances from
+ * templates. If your template's data model contains an instance of
+ * StaticModels (named, say <tt>StaticModels</tt>), then you can
+ * instantiate an arbitrary StaticModel using get syntax (i.e.
+ * <tt>StaticModels["java.lang.System"].currentTimeMillis()</tt>).
+ */
+class StaticModels extends ClassBasedModelFactory {
+    
+    StaticModels(DefaultObjectWrapper wrapper) {
+        super(wrapper);
+    }
+
+    @Override
+    protected TemplateModel createModel(Class clazz) 
+    throws TemplateModelException {
+        return new StaticModel(clazz, getWrapper());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/StringModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/StringModel.java b/src/main/java/org/apache/freemarker/core/model/impl/StringModel.java
new file mode 100644
index 0000000..499c291
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/StringModel.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+/**
+ * Subclass of {@link BeanModel} that exposes the return value of the {@link
+ * java.lang.Object#toString()} method through the {@link TemplateScalarModel}
+ * interface.
+ */
+public class StringModel extends BeanModel
+implements TemplateScalarModel {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new StringModel(object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+
+    /**
+     * Creates a new model that wraps the specified object with BeanModel + scalar
+     * functionality.
+     * @param object the object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     */
+    public StringModel(Object object, DefaultObjectWrapper wrapper) {
+        super(object, wrapper);
+    }
+
+    /**
+     * Returns the result of calling {@link Object#toString()} on the wrapped
+     * object.
+     */
+    @Override
+    public String getAsString() {
+        return object.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java b/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java
new file mode 100644
index 0000000..d81ab74
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java
@@ -0,0 +1,130 @@
+/*
+ * 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.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Flag values and masks for "type flags". "Type flags" is a set of bits that store information about the possible
+ * destination types at a parameter position of overloaded methods. 
+ */
+class TypeFlags {
+
+    /**
+     * Indicates that the unwrapping hint will not be a specific numerical type; it must not be set if there's no
+     * numerical type at the given parameter index.
+     */
+    static final int WIDENED_NUMERICAL_UNWRAPPING_HINT = 1;
+    
+    static final int BYTE = 4;
+    static final int SHORT = 8;
+    static final int INTEGER = 16;
+    static final int LONG = 32;
+    static final int FLOAT = 64;
+    static final int DOUBLE = 128;
+    static final int BIG_INTEGER = 256;
+    static final int BIG_DECIMAL = 512;
+    static final int UNKNOWN_NUMERICAL_TYPE = 1024;
+
+    static final int ACCEPTS_NUMBER = 0x800;
+    static final int ACCEPTS_DATE = 0x1000;
+    static final int ACCEPTS_STRING = 0x2000;
+    static final int ACCEPTS_BOOLEAN = 0x4000;
+    static final int ACCEPTS_MAP = 0x8000;
+    static final int ACCEPTS_LIST = 0x10000;
+    static final int ACCEPTS_SET = 0x20000;
+    static final int ACCEPTS_ARRAY = 0x40000;
+    
+    /**
+     * Indicates the presence of the char or Character type
+     */
+    static final int CHARACTER = 0x80000;
+    
+    static final int ACCEPTS_ANY_OBJECT = ACCEPTS_NUMBER | ACCEPTS_DATE | ACCEPTS_STRING | ACCEPTS_BOOLEAN
+            | ACCEPTS_MAP | ACCEPTS_LIST | ACCEPTS_SET | ACCEPTS_ARRAY;
+    
+    static final int MASK_KNOWN_INTEGERS = BYTE | SHORT | INTEGER | LONG | BIG_INTEGER;
+    static final int MASK_KNOWN_NONINTEGERS = FLOAT | DOUBLE | BIG_DECIMAL;
+    static final int MASK_ALL_KNOWN_NUMERICALS = MASK_KNOWN_INTEGERS | MASK_KNOWN_NONINTEGERS;
+    static final int MASK_ALL_NUMERICALS = MASK_ALL_KNOWN_NUMERICALS | UNKNOWN_NUMERICAL_TYPE;
+    
+    static int classToTypeFlags(Class pClass) {
+        // We start with the most frequent cases  
+        if (pClass == Object.class) {
+            return ACCEPTS_ANY_OBJECT;
+        } else if (pClass == String.class) {
+            return ACCEPTS_STRING;
+        } else if (pClass.isPrimitive()) {
+            if (pClass == Integer.TYPE) return INTEGER | ACCEPTS_NUMBER;
+            else if (pClass == Long.TYPE) return LONG | ACCEPTS_NUMBER;
+            else if (pClass == Double.TYPE) return DOUBLE | ACCEPTS_NUMBER;
+            else if (pClass == Float.TYPE) return FLOAT | ACCEPTS_NUMBER;
+            else if (pClass == Byte.TYPE) return BYTE | ACCEPTS_NUMBER;
+            else if (pClass == Short.TYPE) return SHORT | ACCEPTS_NUMBER;
+            else if (pClass == Character.TYPE) return CHARACTER;
+            else if (pClass == Boolean.TYPE) return ACCEPTS_BOOLEAN;
+            else return 0;
+        } else if (Number.class.isAssignableFrom(pClass)) {
+            if (pClass == Integer.class) return INTEGER | ACCEPTS_NUMBER;
+            else if (pClass == Long.class) return LONG | ACCEPTS_NUMBER;
+            else if (pClass == Double.class) return DOUBLE | ACCEPTS_NUMBER;
+            else if (pClass == Float.class) return FLOAT | ACCEPTS_NUMBER;
+            else if (pClass == Byte.class) return BYTE | ACCEPTS_NUMBER;
+            else if (pClass == Short.class) return SHORT | ACCEPTS_NUMBER;
+            else if (BigDecimal.class.isAssignableFrom(pClass)) return BIG_DECIMAL | ACCEPTS_NUMBER;
+            else if (BigInteger.class.isAssignableFrom(pClass)) return BIG_INTEGER | ACCEPTS_NUMBER;
+            else return UNKNOWN_NUMERICAL_TYPE | ACCEPTS_NUMBER;
+        } else if (pClass.isArray()) {
+            return ACCEPTS_ARRAY;
+        } else {
+            int flags = 0;
+            if (pClass.isAssignableFrom(String.class)) {
+                flags |= ACCEPTS_STRING;
+            }
+            if (pClass.isAssignableFrom(Date.class)) {
+                flags |= ACCEPTS_DATE;
+            }
+            if (pClass.isAssignableFrom(Boolean.class)) {
+                flags |= ACCEPTS_BOOLEAN;
+            }
+            if (pClass.isAssignableFrom(Map.class)) {
+                flags |= ACCEPTS_MAP;
+            }
+            if (pClass.isAssignableFrom(List.class)) {
+                flags |= ACCEPTS_LIST;
+            }
+            if (pClass.isAssignableFrom(Set.class)) {
+                flags |= ACCEPTS_SET;
+            }
+            
+            if (pClass == Character.class) {
+                flags |= CHARACTER;
+            }
+            
+            return flags;
+        } 
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java
new file mode 100644
index 0000000..b54ddbf
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.slf4j.Logger;
+
+class UnsafeMethods {
+    
+    private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
+    private static final String UNSAFE_METHODS_PROPERTIES = "unsafeMethods.properties";
+    private static final Set UNSAFE_METHODS = createUnsafeMethodsSet();
+    
+    private UnsafeMethods() { }
+    
+    static boolean isUnsafeMethod(Method method) {
+        return UNSAFE_METHODS.contains(method);        
+    }
+    
+    private static Set createUnsafeMethodsSet() {
+        Properties props = new Properties();
+        InputStream in = DefaultObjectWrapper.class.getResourceAsStream(UNSAFE_METHODS_PROPERTIES);
+        if (in == null) {
+            throw new IllegalStateException("Class loader resource not found: "
+                        + DefaultObjectWrapper.class.getPackage().getName() + UNSAFE_METHODS_PROPERTIES);
+        }
+        String methodSpec = null;
+        try {
+            try {
+                props.load(in);
+            } finally {
+                in.close();
+            }
+            Set set = new HashSet(props.size() * 4 / 3, 1f);
+            Map primClasses = createPrimitiveClassesMap();
+            for (Iterator iterator = props.keySet().iterator(); iterator.hasNext(); ) {
+                methodSpec = (String) iterator.next();
+                try {
+                    set.add(parseMethodSpec(methodSpec, primClasses));
+                } catch (ClassNotFoundException | NoSuchMethodException e) {
+                    LOG.debug("Failed to get unsafe method", e);
+                }
+            }
+            return set;
+        } catch (Exception e) {
+            throw new RuntimeException("Could not load unsafe method " + methodSpec + " " + e.getClass().getName() + " " + e.getMessage());
+        }
+    }
+
+    private static Method parseMethodSpec(String methodSpec, Map primClasses)
+    throws ClassNotFoundException,
+        NoSuchMethodException {
+        int brace = methodSpec.indexOf('(');
+        int dot = methodSpec.lastIndexOf('.', brace);
+        Class clazz = _ClassUtil.forName(methodSpec.substring(0, dot));
+        String methodName = methodSpec.substring(dot + 1, brace);
+        String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 1);
+        StringTokenizer tok = new StringTokenizer(argSpec, ",");
+        int argcount = tok.countTokens();
+        Class[] argTypes = new Class[argcount];
+        for (int i = 0; i < argcount; i++) {
+            String argClassName = tok.nextToken();
+            argTypes[i] = (Class) primClasses.get(argClassName);
+            if (argTypes[i] == null) {
+                argTypes[i] = _ClassUtil.forName(argClassName);
+            }
+        }
+        return clazz.getMethod(methodName, argTypes);
+    }
+
+    private static Map createPrimitiveClassesMap() {
+        Map map = new HashMap();
+        map.put("boolean", Boolean.TYPE);
+        map.put("byte", Byte.TYPE);
+        map.put("char", Character.TYPE);
+        map.put("short", Short.TYPE);
+        map.put("int", Integer.TYPE);
+        map.put("long", Long.TYPE);
+        map.put("float", Float.TYPE);
+        map.put("double", Double.TYPE);
+        return map;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_EnumModels.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_EnumModels.java b/src/main/java/org/apache/freemarker/core/model/impl/_EnumModels.java
new file mode 100644
index 0000000..19efe76
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/_EnumModels.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Don't use this class; it's only public to work around Google App Engine Java
+ * compliance issues. FreeMarker developers only: treat this class as package-visible.
+ */
+public class _EnumModels extends ClassBasedModelFactory {
+
+    public _EnumModels(DefaultObjectWrapper wrapper) {
+        super(wrapper);
+    }
+    
+    @Override
+    protected TemplateModel createModel(Class clazz) {
+        Object[] obj = clazz.getEnumConstants();
+        if (obj == null) {
+            // Return null - it'll manifest itself as undefined in the template.
+            // We're doing this rather than throw an exception as this way 
+            // people can use someEnumModel?default({}) to gracefully fall back 
+            // to an empty hash if they want to.
+            return null;
+        }
+        Map map = new LinkedHashMap();
+        for (Object anObj : obj) {
+            Enum value = (Enum) anObj;
+            map.put(value.name(), value);
+        }
+        return new SimpleMapModel(map, getWrapper());
+    }
+}


[04/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperMiscTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperMiscTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperMiscTest.java
deleted file mode 100644
index fd20bd6..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperMiscTest.java
+++ /dev/null
@@ -1,57 +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.beans;
-
-import static org.junit.Assert.*;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class BeansWrapperMiscTest {
-
-    @Test
-    public void booleans() throws Exception {
-        final BeansWrapper bw = new BeansWrapper(Configuration.VERSION_3_0_0);
-
-        assertTrue(((TemplateBooleanModel) bw.wrap(Boolean.TRUE)).getAsBoolean());
-        assertFalse(((TemplateBooleanModel) bw.wrap(Boolean.FALSE)).getAsBoolean());
-        
-        TemplateHashModel tm = (TemplateHashModel) bw.wrap(Boolean.TRUE);
-        assertNotNull(tm.get("hashCode"));
-        assertNotNull(tm.get("class"));
-        bw.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
-        assertNull(tm.get("hashCode"));
-        assertNotNull(tm.get("class"));
-        bw.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
-        assertNull(tm.get("hashCode"));
-        assertNull(tm.get("class"));
-        bw.setExposureLevel(BeansWrapper.EXPOSE_ALL);
-        assertNotNull(tm.get("hashCode"));
-        assertNotNull(tm.get("class"));
-        
-        assertSame(tm, bw.wrap(Boolean.TRUE));
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperReadOnlyTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperReadOnlyTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperReadOnlyTest.java
deleted file mode 100644
index 2ab748f..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperReadOnlyTest.java
+++ /dev/null
@@ -1,95 +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.beans;
-
-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.model.ObjectWrapper;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.util.WriteProtectable;
-import org.apache.freemarker.core.util._ClassUtil;
-
-import junit.framework.TestCase;
-
-/**
- * Tests if all JavaBean properties of the standard {@link ObjectWrapper} classes are locked by
- * {@link WriteProtectable#writeProtect()}. 
- */
-public class BeansWrapperReadOnlyTest extends TestCase {
-
-    private static final String EXPECTED_MESSAGE_PART = "write protected";
-
-    public BeansWrapperReadOnlyTest(String name) {
-        super(name);
-    }
-    
-    public void testBeansWrapper() throws Exception {
-        BeansWrapper bw = new BeansWrapper(Configuration.VERSION_3_0_0);
-        bw.writeProtect();
-        checkAllPropertiesReadOnly(bw);
-    }
-
-    public void testDefaultObjectWrapper() throws Exception {
-        BeansWrapper bw = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
-        bw.writeProtect();
-        checkAllPropertiesReadOnly(bw);        
-    }
-    
-    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/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperSingletonsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperSingletonsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperSingletonsTest.java
deleted file mode 100644
index 027e6f2..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperSingletonsTest.java
+++ /dev/null
@@ -1,743 +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.beans;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.lang.ref.Reference;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.impl.SimpleObjectWrapper;
-import org.apache.freemarker.core.model.impl._ModelImplApi;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecision;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecisionInput;
-import org.apache.freemarker.test.util.TestUtil;
-
-import junit.framework.TestCase;
-
-public class BeansWrapperSingletonsTest extends TestCase {
-
-    public BeansWrapperSingletonsTest(String name) {
-        super(name);
-    }
-    
-    @Override
-    protected void setUp() throws Exception {
-        BeansWrapperBuilder.clearInstanceCache();  // otherwise ClassInrospector cache couldn't become cleared in reality
-        _ModelImplApi.DefaultObjectWrapperFactory_clearInstanceCache();
-        BeansWrapperBuilder.clearInstanceCache();
-    }
-
-    public void testBeansWrapperFactoryEquals() throws Exception {
-        assertEquals(Configuration.VERSION_3_0_0, new BeansWrapperBuilder(Configuration.VERSION_3_0_0).getIncompatibleImprovements());
-        try {
-            new BeansWrapperBuilder(TestUtil.getClosestFutureVersion());
-            fail("Maybe you need to update this test for the new FreeMarker version");
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), containsString("upgrade"));
-        }
-
-        BeansWrapperBuilder pa1;
-        BeansWrapperBuilder pa2;
-        
-        pa1 = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-        pa2 = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-        assertEquals(pa1, pa2);
-        
-        pa1.setSimpleMapWrapper(true);
-        assertNotEquals(pa1, pa2);
-        assertFalse(pa1.hashCode() == pa2.hashCode());
-        pa2.setSimpleMapWrapper(true);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-        
-        pa1.setExposeFields(true);
-        assertNotEquals(pa1, pa2);
-        assertFalse(pa1.hashCode() == pa2.hashCode());
-        pa2.setExposeFields(true);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-        
-        pa1.setExposureLevel(0);
-        assertNotEquals(pa1, pa2);
-        assertFalse(pa1.hashCode() == pa2.hashCode());
-        pa2.setExposureLevel(0);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-        
-        pa1.setExposureLevel(1);
-        assertNotEquals(pa1, pa2);
-        assertFalse(pa1.hashCode() == pa2.hashCode());
-        pa2.setExposureLevel(1);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-        
-        pa1.setDefaultDateType(TemplateDateModel.DATE);
-        assertNotEquals(pa1, pa2);
-        pa2.setDefaultDateType(TemplateDateModel.DATE);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-        
-        pa1.setStrict(true);
-        assertNotEquals(pa1, pa2);
-        assertFalse(pa1.hashCode() == pa2.hashCode());
-        pa2.setStrict(true);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-
-        pa1.setUseModelCache(true);
-        assertNotEquals(pa1, pa2);
-        assertFalse(pa1.hashCode() == pa2.hashCode());
-        pa2.setUseModelCache(true);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-        
-        AlphabeticalMethodSorter ms = new AlphabeticalMethodSorter(true);
-        pa1.setMethodSorter(ms);
-        assertNotEquals(pa1, pa2);
-        pa2.setMethodSorter(ms);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-        
-        MethodAppearanceFineTuner maft = new MethodAppearanceFineTuner() {
-            @Override
-            public void process(MethodAppearanceDecisionInput in, MethodAppearanceDecision out) { }
-        };
-        pa1.setMethodAppearanceFineTuner(maft);
-        assertNotEquals(pa1, pa2);
-        pa2.setMethodAppearanceFineTuner(maft);
-        assertEquals(pa1, pa2);
-        assertTrue(pa1.hashCode() == pa2.hashCode());
-    }
-    
-    public void testBeansWrapperFactoryProducts() throws Exception {
-        List<BeansWrapper> hardReferences = new LinkedList<>();
-        
-        assertEquals(0, getBeansWrapperInstanceCacheSize());
-        
-        {
-            BeansWrapper bw = getBeansWrapperWithSetting(Configuration.VERSION_3_0_0, true);
-            assertEquals(1, getBeansWrapperInstanceCacheSize());
-            assertSame(bw.getClass(), BeansWrapper.class);
-            assertEquals(Configuration.VERSION_3_0_0, bw.getIncompatibleImprovements());
-            assertTrue(bw.isWriteProtected());
-            assertTrue(bw.isSimpleMapWrapper());
-            assertThat(bw.wrap(new HashMap()), instanceOf(SimpleMapModel.class));
-            assertFalse(bw.isStrict());
-            assertFalse(bw.getUseModelCache());
-            assertEquals(TemplateDateModel.UNKNOWN, bw.getDefaultDateType());
-            assertSame(bw, bw.getOuterIdentity());
-            assertTrue(bw.isClassIntrospectionCacheRestricted());
-            assertNull(bw.getMethodAppearanceFineTuner());
-            assertNull(bw.getMethodSorter());
-            
-            try {
-                bw.setExposeFields(true);  // can't modify the settings of a (potential) singleton
-                fail();
-            } catch (IllegalStateException e) {
-                assertThat(e.getMessage(), containsString("modify"));
-            }
-            
-            assertSame(bw, getBeansWrapperWithSetting(Configuration.VERSION_3_0_0, true));
-            assertEquals(1, getBeansWrapperInstanceCacheSize());
-            
-            hardReferences.add(bw);            
-        }
-        
-        {
-            BeansWrapper bw = getBeansWrapperWithSetting(Configuration.VERSION_3_0_0, false);
-            assertEquals(2, getBeansWrapperInstanceCacheSize());
-            assertSame(bw.getClass(), BeansWrapper.class);
-            assertEquals(Configuration.VERSION_3_0_0, bw.getIncompatibleImprovements());
-            assertTrue(bw.isWriteProtected());
-            assertFalse(bw.isSimpleMapWrapper());
-            assertThat(bw.wrap(new HashMap()), instanceOf(MapModel.class));
-            
-            assertSame(bw, getBeansWrapperWithSetting(Configuration.VERSION_3_0_0, false));
-            
-            hardReferences.add(bw);            
-        }
-        
-        {
-            BeansWrapperBuilder factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
-            BeansWrapper bw = factory.build();
-            BeansWrapper bw2 = factory.build();
-            assertEquals(3, getBeansWrapperInstanceCacheSize());
-            assertSame(bw, bw2);
-            
-            assertSame(bw.getClass(), BeansWrapper.class);
-            assertEquals(Configuration.VERSION_3_0_0, bw.getIncompatibleImprovements());
-            assertTrue(bw.isWriteProtected());
-            assertFalse(bw.isSimpleMapWrapper());
-            assertThat(bw.wrap(new HashMap()), instanceOf(MapModel.class));
-            assertEquals(BeansWrapper.EXPOSE_PROPERTIES_ONLY, bw.getExposureLevel());
-            assertFalse(bw.isStrict());
-            assertEquals(TemplateDateModel.UNKNOWN, bw.getDefaultDateType());
-            assertSame(bw, bw.getOuterIdentity());
-            
-            hardReferences.add(bw);            
-        }
-        
-        {
-            BeansWrapperBuilder factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setExposeFields(true);
-            BeansWrapper bw = factory.build();
-            BeansWrapper bw2 = factory.build();
-            assertEquals(4, getBeansWrapperInstanceCacheSize());
-            assertSame(bw, bw2);
-            
-            assertSame(bw.getClass(), BeansWrapper.class);
-            assertEquals(Configuration.VERSION_3_0_0, bw.getIncompatibleImprovements());
-            assertTrue(bw.isWriteProtected());
-            assertFalse(bw.isSimpleMapWrapper());
-            assertTrue(bw.isExposeFields());
-            
-            hardReferences.add(bw);            
-        }
-        
-        {
-            BeansWrapperBuilder factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setStrict(true);
-            factory.setDefaultDateType(TemplateDateModel.DATETIME);
-            factory.setOuterIdentity(new SimpleObjectWrapper(Configuration.VERSION_3_0_0));
-            BeansWrapper bw = factory.build();
-            assertEquals(5, getBeansWrapperInstanceCacheSize());
-            assertTrue(bw.isStrict());
-            assertEquals(TemplateDateModel.DATETIME, bw.getDefaultDateType());
-            assertSame(SimpleObjectWrapper.class, bw.getOuterIdentity().getClass());
-            
-            hardReferences.add(bw);            
-        }
-        
-        // Effect of reference and cache clearings:
-        {
-            BeansWrapper bw1 = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-            assertEquals(5, getBeansWrapperInstanceCacheSize());
-            assertEquals(5, getBeansWrapperNonClearedInstanceCacheSize());
-            
-            clearBeansWrapperInstanceCacheReferences(false);
-            assertEquals(5, getBeansWrapperInstanceCacheSize());
-            assertEquals(0, getBeansWrapperNonClearedInstanceCacheSize());
-            
-            BeansWrapper bw2 = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-            assertNotSame(bw1, bw2);
-            assertEquals(5, getBeansWrapperInstanceCacheSize());
-            assertEquals(1, getBeansWrapperNonClearedInstanceCacheSize());
-            
-            assertSame(bw2, new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build());
-            assertEquals(1, getBeansWrapperNonClearedInstanceCacheSize());
-            
-            clearBeansWrapperInstanceCacheReferences(true);
-            BeansWrapper bw3 = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-            assertNotSame(bw2, bw3);
-            assertEquals(1, getBeansWrapperInstanceCacheSize());
-            assertEquals(1, getBeansWrapperNonClearedInstanceCacheSize());
-        }
-
-        {
-            BeansWrapperBuilder factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setUseModelCache(true);
-            BeansWrapper bw = factory.build();
-            assertTrue(bw.getUseModelCache());
-            assertEquals(2, getBeansWrapperInstanceCacheSize());
-            
-            hardReferences.add(bw);            
-        }
-        
-        assertTrue(hardReferences.size() != 0);  // just to save it from GC until this line        
-    }
-    
-    private BeansWrapper getBeansWrapperWithSetting(Version ici, boolean simpleMapWrapper) {
-        BeansWrapperBuilder f = new BeansWrapperBuilder(ici);
-        f.setSimpleMapWrapper(simpleMapWrapper);
-        return f.build();
-    }
-
-    public void testMultipleTCCLs() {
-        List<BeansWrapper> hardReferences = new LinkedList<>();
-        
-        assertEquals(0, getBeansWrapperInstanceCacheSize());
-        
-        BeansWrapper bw1 = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-        assertEquals(1, getBeansWrapperInstanceCacheSize());
-        hardReferences.add(bw1);
-        
-        ClassLoader oldTCCL = Thread.currentThread().getContextClassLoader();
-        // Doesn't mater what, just be different from oldTCCL: 
-        ClassLoader newTCCL = oldTCCL == null ? getClass().getClassLoader() : null;
-        
-        BeansWrapper bw2;
-        Thread.currentThread().setContextClassLoader(newTCCL);
-        try {
-            bw2 = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-            assertEquals(2, getBeansWrapperInstanceCacheSize());
-            hardReferences.add(bw2);
-            
-            assertNotSame(bw1, bw2);
-            assertSame(bw2, new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build());
-        } finally {
-            Thread.currentThread().setContextClassLoader(oldTCCL);
-        }
-        
-        assertSame(bw1, new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build());
-        assertEquals(2, getBeansWrapperInstanceCacheSize());
-
-        BeansWrapper bw3;
-        Thread.currentThread().setContextClassLoader(newTCCL);
-        try {
-            assertSame(bw2, new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build());
-            
-            BeansWrapperBuilder bwb = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            bwb.setExposeFields(true);
-            bw3 = bwb.build();
-            assertEquals(3, getBeansWrapperInstanceCacheSize());
-            hardReferences.add(bw3);
-        } finally {
-            Thread.currentThread().setContextClassLoader(oldTCCL);
-        }
-        
-        {
-            BeansWrapperBuilder bwb = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            bwb.setExposeFields(true);
-            BeansWrapper bw4 = bwb.build();
-            assertEquals(4, getBeansWrapperInstanceCacheSize());
-            assertNotSame(bw3, bw4);
-            hardReferences.add(bw4);
-        }
-        
-        assertTrue(hardReferences.size() != 0);  // just to save it from GC until this line        
-    }
-    
-    public void testClassInrospectorCache() throws TemplateModelException {
-        assertFalse(new BeansWrapper(Configuration.VERSION_3_0_0).isClassIntrospectionCacheRestricted());
-        assertTrue(new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build().isClassIntrospectionCacheRestricted());
-        
-        ClassIntrospectorBuilder.clearInstanceCache();
-        BeansWrapperBuilder.clearInstanceCache();
-        checkClassIntrospectorCacheSize(0);
-        
-        List<BeansWrapper> hardReferences = new LinkedList<>();
-        BeansWrapperBuilder factory;
-        
-        {
-            factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            
-            BeansWrapper bw1 = factory.build();
-            checkClassIntrospectorCacheSize(1);
-            
-            factory.setExposureLevel(BeansWrapper.EXPOSE_SAFE);  // this was already set to this
-            factory.setSimpleMapWrapper(true);  // this shouldn't matter for the introspection cache
-            BeansWrapper bw2 = factory.build();
-            checkClassIntrospectorCacheSize(1);
-            
-            assertSame(bw2.getClassIntrospector(), bw1.getClassIntrospector());
-            assertNotSame(bw1, bw2);
-            
-            // Wrapping tests:
-            assertFalse(exposesFields(bw1));
-            assertTrue(exposesProperties(bw1));
-            assertTrue(exposesMethods(bw1));
-            assertFalse(exposesUnsafe(bw1));
-            assertFalse(isSimpleMapWrapper(bw1));
-            assertTrue(bw1.isClassIntrospectionCacheRestricted());
-            // Prevent introspection cache GC:
-            hardReferences.add(bw1);
-            hardReferences.add(bw2);
-        }
-        
-        {
-            factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setExposeFields(true);
-            BeansWrapper bw = factory.build();
-            checkClassIntrospectorCacheSize(2);
-            // Wrapping tests:
-            assertTrue(exposesFields(bw));
-            assertTrue(exposesProperties(bw));
-            assertTrue(exposesMethods(bw));
-            assertFalse(exposesUnsafe(bw));
-            assertFalse(isSimpleMapWrapper(bw));
-            // Prevent introspection cache GC:
-            hardReferences.add(bw);
-        }
-
-        {
-            factory.setExposureLevel(BeansWrapper.EXPOSE_ALL);
-            BeansWrapper bw = factory.build();
-            checkClassIntrospectorCacheSize(3);
-            // Wrapping tests:
-            assertTrue(exposesFields(bw));
-            assertTrue(exposesProperties(bw));
-            assertTrue(exposesMethods(bw));
-            assertTrue(exposesUnsafe(bw));
-            assertFalse(isSimpleMapWrapper(bw));
-            // Prevent introspection cache GC:
-            hardReferences.add(bw);
-        }
-        
-        {
-            factory.setExposeFields(false);
-            BeansWrapper bw = factory.build();
-            checkClassIntrospectorCacheSize(4);
-            // Wrapping tests:
-            assertFalse(exposesFields(bw));
-            assertTrue(exposesProperties(bw));
-            assertTrue(exposesMethods(bw));
-            assertTrue(exposesUnsafe(bw));
-            assertFalse(isSimpleMapWrapper(bw));
-            // Prevent introspection cache GC:
-            hardReferences.add(bw);
-        }
-        
-        {
-            factory.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
-            BeansWrapper bw = factory.build();
-            checkClassIntrospectorCacheSize(5);
-            // Wrapping tests:
-            assertFalse(exposesFields(bw));
-            assertFalse(exposesProperties(bw));
-            assertFalse(exposesMethods(bw));
-            assertFalse(exposesUnsafe(bw));
-            assertFalse(isSimpleMapWrapper(bw));
-            // Prevent introspection cache GC:
-            hardReferences.add(bw);
-        }
-
-        {
-            factory.setExposeFields(true);
-            BeansWrapper bw = factory.build();
-            checkClassIntrospectorCacheSize(6);
-            // Wrapping tests:
-            assertTrue(exposesFields(bw));
-            assertFalse(exposesProperties(bw));
-            assertFalse(exposesMethods(bw));
-            assertFalse(exposesUnsafe(bw));
-            assertFalse(isSimpleMapWrapper(bw));
-            // Prevent introspection cache GC:
-            hardReferences.add(bw);
-        }
-
-        {
-            factory.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
-            BeansWrapper bw = factory.build();
-            checkClassIntrospectorCacheSize(7);
-            // Wrapping tests:
-            assertTrue(exposesFields(bw));
-            assertTrue(exposesProperties(bw));
-            assertFalse(exposesMethods(bw));
-            assertFalse(exposesUnsafe(bw));
-            assertFalse(isSimpleMapWrapper(bw));
-            // Prevent introspection cache GC:
-            hardReferences.add(bw);
-        }
-        
-        {
-            factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setExposeFields(false);
-            factory.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
-            
-            BeansWrapper bw1 = factory.build();
-            checkClassIntrospectorCacheSize(8);
-            ClassIntrospector ci1 = bw1.getClassIntrospector();
-            
-            factory.setSimpleMapWrapper(true);  // Shouldn't mater
-            BeansWrapper bw2 = factory.build();
-            ClassIntrospector ci2 = bw2.getClassIntrospector();
-            checkClassIntrospectorCacheSize(8);
-            
-            assertSame(ci1, ci2);
-            assertNotSame(bw1, bw2);
-            
-            // Wrapping tests:
-            assertFalse(exposesFields(bw1));
-            assertTrue(exposesProperties(bw1));
-            assertFalse(exposesMethods(bw1));
-            assertFalse(exposesUnsafe(bw1));
-            assertFalse(isSimpleMapWrapper(bw1));
-            
-            // Prevent introspection cache GC:
-            hardReferences.add(bw1);
-            hardReferences.add(bw2);
-        }
-        
-        BeansWrapperBuilder.clearInstanceCache();  // otherwise ClassInrospector cache couldn't become cleared in reality
-        _ModelImplApi.DefaultObjectWrapperFactory_clearInstanceCache();
-        clearClassIntrospectorInstanceCacheReferences(false);
-        checkClassIntrospectorCacheSize(8);
-        assertEquals(0, getClassIntrospectorNonClearedInstanceCacheSize());
-
-        {
-            factory.setSimpleMapWrapper(false);
-            factory.setExposeFields(false);
-            
-            BeansWrapper bw1 = factory.build();
-            checkClassIntrospectorCacheSize(8);
-            assertEquals(1, getClassIntrospectorNonClearedInstanceCacheSize());
-            ClassIntrospector ci1 = bw1.getClassIntrospector();
-            
-            factory.setSimpleMapWrapper(true);  // Shouldn't mater
-            BeansWrapper bw2 = factory.build();
-            ClassIntrospector ci2 = bw2.getClassIntrospector();
-            
-            assertSame(ci1, ci2);
-            assertNotSame(bw1, bw2);
-            
-            // Wrapping tests:
-            assertFalse(exposesFields(bw1));
-            assertTrue(exposesProperties(bw1));
-            assertFalse(exposesMethods(bw1));
-            assertFalse(exposesUnsafe(bw1));
-            assertFalse(isSimpleMapWrapper(bw1));
-            
-            // Prevent introspection cache GC:
-            hardReferences.add(bw1);
-            hardReferences.add(bw2);
-        }
-        
-        {
-            factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            BeansWrapper bw = factory.build();
-            checkClassIntrospectorCacheSize(8);
-            assertEquals(2, getClassIntrospectorNonClearedInstanceCacheSize());
-            // Wrapping tests:
-            assertFalse(exposesFields(bw));
-            assertTrue(exposesProperties(bw));
-            assertTrue(exposesMethods(bw));
-            assertFalse(exposesUnsafe(bw));
-            // Prevent introspection cache GC:
-            hardReferences.add(bw);
-        }
-
-        clearClassIntrospectorInstanceCacheReferences(true);
-        checkClassIntrospectorCacheSize(8);
-        assertEquals(0, getClassIntrospectorNonClearedInstanceCacheSize());
-        
-        {
-            factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setExposeFields(true);
-            BeansWrapper bw = factory.build();
-            checkClassIntrospectorCacheSize(1);
-            // Wrapping tests:
-            assertTrue(exposesFields(bw));
-            assertTrue(exposesProperties(bw));
-            assertTrue(exposesMethods(bw));
-            assertFalse(exposesUnsafe(bw));
-            // Prevent introspection cache GC:
-            hardReferences.add(bw);
-        }
-        
-        {
-            factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
-                @Override
-                public void process(MethodAppearanceDecisionInput in, MethodAppearanceDecision out) {
-                }
-            });  // spoils ClassIntrospector() sharing
-            
-            BeansWrapper bw1 = factory.build();
-            assertSame(bw1, factory.build());
-            
-            factory.setSimpleMapWrapper(true);
-            BeansWrapper bw2 = factory.build();
-            checkClassIntrospectorCacheSize(1);
-            assertNotSame(bw1, bw2);
-            assertNotSame(bw1.getClassIntrospector(), bw2.getClassIntrospector());
-            assertTrue(bw1.isClassIntrospectionCacheRestricted());
-            assertTrue(bw2.isClassIntrospectionCacheRestricted());
-            
-            // Wrapping tests:
-            assertFalse(exposesFields(bw1));
-            assertFalse(exposesFields(bw2));
-            assertTrue(exposesProperties(bw1));
-            assertTrue(exposesProperties(bw2));
-            assertTrue(exposesMethods(bw1));
-            assertTrue(exposesMethods(bw2));
-            assertFalse(exposesUnsafe(bw1));
-            assertFalse(exposesUnsafe(bw2));
-            assertFalse(isSimpleMapWrapper(bw1));
-            assertTrue(isSimpleMapWrapper(bw2));
-        }
-
-        {
-            factory = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-            factory.setMethodAppearanceFineTuner(
-                    GetlessMethodsAsPropertyGettersRule.INSTANCE);  // doesn't spoils sharing
-            
-            BeansWrapper bw1 = factory.build();
-            assertSame(bw1, factory.build());
-            checkClassIntrospectorCacheSize(2);
-            
-            factory.setSimpleMapWrapper(true);
-            BeansWrapper bw2 = factory.build();
-            checkClassIntrospectorCacheSize(2);
-            
-            assertNotSame(bw1, bw2);
-            assertSame(bw1.getClassIntrospector(), bw2.getClassIntrospector());  // !
-            assertTrue(bw1.isClassIntrospectionCacheRestricted());
-            assertTrue(bw2.isClassIntrospectionCacheRestricted());
-            
-            // Wrapping tests:
-            assertFalse(exposesFields(bw1));
-            assertFalse(exposesFields(bw2));
-            assertTrue(exposesProperties(bw1));
-            assertTrue(exposesProperties(bw2));
-            assertTrue(exposesMethods(bw1));
-            assertTrue(exposesMethods(bw2));
-            assertFalse(exposesUnsafe(bw1));
-            assertFalse(exposesUnsafe(bw2));
-            assertFalse(isSimpleMapWrapper(bw1));
-            assertTrue(isSimpleMapWrapper(bw2));
-        }
-        
-        assertTrue(hardReferences.size() != 0);  // just to save it from GC until this line        
-    }
-    
-    private void checkClassIntrospectorCacheSize(int expectedSize) {
-        assertEquals(expectedSize, getClassIntrospectorInstanceCacheSize());
-    }
-
-    private void assertNotEquals(Object o1, Object o2) {
-        assertFalse(o1.equals(o2));
-    }
-    
-    public class C {
-        
-        public String foo = "FOO";
-        
-        public String getBar() {
-            return "BAR";
-        }
-        
-    }
-
-    private boolean isSimpleMapWrapper(BeansWrapper bw) throws TemplateModelException {
-        return bw.wrap(new HashMap()) instanceof SimpleMapModel;
-    }
-    
-    private boolean exposesFields(BeansWrapper bw) throws TemplateModelException {
-        TemplateHashModel thm = (TemplateHashModel) bw.wrap(new C());
-        TemplateScalarModel r = (TemplateScalarModel) thm.get("foo");
-        if (r == null) return false;
-        assertEquals("FOO", r.getAsString());
-        return true;
-    }
-
-    private boolean exposesProperties(BeansWrapper bw) throws TemplateModelException {
-        TemplateHashModel thm = (TemplateHashModel) bw.wrap(new C());
-        TemplateScalarModel r = (TemplateScalarModel) thm.get("bar");
-        if (r == null) return false;
-        assertEquals("BAR", r.getAsString());
-        return true;
-    }
-
-    private boolean exposesMethods(BeansWrapper bw) throws TemplateModelException {
-        TemplateHashModel thm = (TemplateHashModel) bw.wrap(new C());
-        return thm.get("getBar") != null;
-    }
-
-    private boolean exposesUnsafe(BeansWrapper bw) throws TemplateModelException {
-        TemplateHashModel thm = (TemplateHashModel) bw.wrap(new C());
-        return thm.get("wait") != null;
-    }
-    
-    static int getClassIntrospectorInstanceCacheSize() {
-        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
-        synchronized (instanceCache) {
-            return instanceCache.size();
-        }
-    }
-
-    static int getClassIntrospectorNonClearedInstanceCacheSize() {
-        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
-        synchronized (instanceCache) {
-            int cnt = 0;
-            for (Iterator it = instanceCache.values().iterator(); it.hasNext(); ) {
-                if (((Reference) it.next()).get() != null) cnt++;
-            }
-            return cnt;
-        }
-    }
-    
-    static void clearClassIntrospectorInstanceCacheReferences(boolean enqueue) {
-        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
-        synchronized (instanceCache) {
-            for (Iterator it = instanceCache.values().iterator(); it.hasNext(); ) {
-                Reference ref = ((Reference) it.next());
-                ref.clear();
-                if (enqueue) {
-                    ref.enqueue();
-                }
-            }
-        }
-    }
-
-    static int getBeansWrapperInstanceCacheSize() {
-        Map instanceCache = BeansWrapperBuilder.getInstanceCache();
-        synchronized (instanceCache) {
-            int size = 0; 
-            for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
-                size += ((Map) it1.next()).size();
-            }
-            return size;
-        }
-    }
-
-    static int getBeansWrapperNonClearedInstanceCacheSize() {
-        Map instanceCache = BeansWrapperBuilder.getInstanceCache();
-        synchronized (instanceCache) {
-            int cnt = 0;
-            for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
-                Map tcclScope = (Map) it1.next();
-                for (Iterator it2 = tcclScope.values().iterator(); it2.hasNext(); ) {
-                    if (((Reference) it2.next()).get() != null) cnt++;
-                }
-            }
-            return cnt;
-        }
-    }
-    
-    static void clearBeansWrapperInstanceCacheReferences(boolean enqueue) {
-        Map instanceCache = BeansWrapperBuilder.getInstanceCache();
-        synchronized (instanceCache) {
-            for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
-                Map tcclScope = (Map) it1.next();
-                for (Iterator it2 = tcclScope.values().iterator(); it2.hasNext(); ) {
-                    Reference ref = ((Reference) it2.next());
-                    ref.clear();
-                    if (enqueue) {
-                        ref.enqueue();
-                    }
-                }
-            }
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperWithShortedMethods.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperWithShortedMethods.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperWithShortedMethods.java
deleted file mode 100644
index 1a4ea27..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperWithShortedMethods.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.beans;
-
-
-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 BeansWrapperWithShortedMethods extends BeansWrapper {
-    
-    public BeansWrapperWithShortedMethods(boolean desc) {
-        super(Configuration.VERSION_3_0_0);
-        setMethodSorter(new AlphabeticalMethodSorter(desc));
-    }
-
-    public BeansWrapperWithShortedMethods(Version incompatibleImprovements, boolean desc) {
-        super(incompatibleImprovements);
-        setMethodSorter(new AlphabeticalMethodSorter(desc));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/CommonSupertypeForUnwrappingHintTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/CommonSupertypeForUnwrappingHintTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/CommonSupertypeForUnwrappingHintTest.java
deleted file mode 100644
index e7ea63c..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/CommonSupertypeForUnwrappingHintTest.java
+++ /dev/null
@@ -1,129 +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.beans;
-
-import java.io.Serializable;
-import java.util.List;
-
-import org.apache.freemarker.core.model.TemplateModelException;
-
-import junit.framework.TestCase;
-
-public class CommonSupertypeForUnwrappingHintTest extends TestCase {
-    
-    final OverloadedMethodsSubset oms = new DummyOverloadedMethodsSubset();
-
-    public CommonSupertypeForUnwrappingHintTest(String name) {
-        super(name);
-    }
-
-    public void testInterfaces() {
-        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(String.class, Number.class));
-        assertEquals(C1I1.class, oms.getCommonSupertypeForUnwrappingHint(C2ExtC1I1.class, C3ExtC1I1.class));
-        assertEquals(Object.class, oms.getCommonSupertypeForUnwrappingHint(C3I1I2.class, C4I1I2.class));
-        assertEquals(I1.class, oms.getCommonSupertypeForUnwrappingHint(C3I1I2.class, C5I1.class));
-        assertEquals(I1.class, oms.getCommonSupertypeForUnwrappingHint(C3I1I2.class, I1.class));
-        assertEquals(I2.class, oms.getCommonSupertypeForUnwrappingHint(C3I1I2.class, I2.class));
-        assertEquals(I1.class, oms.getCommonSupertypeForUnwrappingHint(I1I2.class, I1.class));
-        assertEquals(I2.class, oms.getCommonSupertypeForUnwrappingHint(I1I2.class, I2.class));
-        assertEquals(CharSequence.class, oms.getCommonSupertypeForUnwrappingHint(String.class, StringBuilder.class));
-        assertEquals(C6.class, oms.getCommonSupertypeForUnwrappingHint(C7ExtC6I1.class, C8ExtC6I1.class));
-    }
-
-    public void testArrayAndOther() {
-        testArrayAndOther(oms);
-    }
-    
-    /** These will be the same with oms and buggy: */
-    private void testArrayAndOther(OverloadedMethodsSubset oms) {
-        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(int[].class, String.class));
-        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(Object[].class, String.class));
-        
-        assertEquals(Object.class, oms.getCommonSupertypeForUnwrappingHint(int[].class, List.class));
-        assertEquals(Object.class, oms.getCommonSupertypeForUnwrappingHint(Object[].class, List.class));
-        
-        assertEquals(int[].class, oms.getCommonSupertypeForUnwrappingHint(int[].class, int[].class));
-        assertEquals(Object[].class, oms.getCommonSupertypeForUnwrappingHint(Object[].class, Object[].class));
-    }
-    
-    public void testArrayAndDifferentArray() {
-        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(int[].class, Object[].class));
-        assertEquals(Serializable.class, oms.getCommonSupertypeForUnwrappingHint(int[].class, long[].class));
-    }
-    
-    public void testPrimitive() {
-        assertEquals(Integer.class, oms.getCommonSupertypeForUnwrappingHint(int.class, Integer.class));
-        assertEquals(Integer.class, oms.getCommonSupertypeForUnwrappingHint(Integer.class, int.class));
-        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(int.class, Long.class));
-        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(Long.class, int.class));
-        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(Integer.class, long.class));
-        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(long.class, Integer.class));
-        assertEquals(Boolean.class, oms.getCommonSupertypeForUnwrappingHint(boolean.class, Boolean.class));
-        assertEquals(Boolean.class, oms.getCommonSupertypeForUnwrappingHint(Boolean.class, boolean.class));
-        assertEquals(Character.class, oms.getCommonSupertypeForUnwrappingHint(char.class, Character.class));
-        assertEquals(Character.class, oms.getCommonSupertypeForUnwrappingHint(Character.class, char.class));
-        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(int.class, short.class));
-        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(short.class, int.class));
-    }
-
-    public void testMisc() {
-        assertEquals(Number.class, oms.getCommonSupertypeForUnwrappingHint(Long.class, Integer.class));
-        assertEquals(char.class, oms.getCommonSupertypeForUnwrappingHint(char.class, char.class));
-        assertEquals(Integer.class, oms.getCommonSupertypeForUnwrappingHint(Integer.class, Integer.class));
-        assertEquals(String.class, oms.getCommonSupertypeForUnwrappingHint(String.class, String.class));
-    }
-    
-    static interface I1 { };
-    static class C1I1 implements I1 { };
-    static class C2ExtC1I1 extends C1I1 { };
-    static class C3ExtC1I1 extends C1I1 { };
-    static interface I2 { };
-    static class C3I1I2 implements I1, I2 { };
-    static class C4I1I2 implements I1, I2 { };
-    static class C5I1 implements I1 { };
-    static interface I1I2 extends I1, I2 { };
-    static class C6 { };
-    static class C7ExtC6I1 extends C6 implements I1 { };
-    static class C8ExtC6I1 extends C6 implements I1 { };
-    
-    private static class DummyOverloadedMethodsSubset extends OverloadedMethodsSubset {
-
-        DummyOverloadedMethodsSubset() {
-            super();
-        }
-
-        @Override
-        Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) {
-            return memberDesc.getParamTypes();
-        }
-
-        @Override
-        void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) {
-            // Do nothing
-        }
-
-        @Override
-        MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, BeansWrapper w) throws TemplateModelException {
-            throw new RuntimeException("Not implemented in this dummy.");
-        }
-        
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperDesc.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperDesc.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperDesc.java
deleted file mode 100644
index bf61205..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperDesc.java
+++ /dev/null
@@ -1,30 +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.beans;
-
-import org.apache.freemarker.core.Configuration;
-
-public class DefaultObjectWrapperDesc extends DefaultObjectWrapperWithSortedMethods {
-
-    public DefaultObjectWrapperDesc() {
-        super(Configuration.VERSION_3_0_0, true);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperInc.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperInc.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperInc.java
deleted file mode 100644
index 60a331a..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperInc.java
+++ /dev/null
@@ -1,30 +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.beans;
-
-import org.apache.freemarker.core.Configuration;
-
-public class DefaultObjectWrapperInc extends DefaultObjectWrapperWithSortedMethods {
-
-    public DefaultObjectWrapperInc() {
-        super(Configuration.VERSION_3_0_0, false);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperWithSortedMethods.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperWithSortedMethods.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperWithSortedMethods.java
deleted file mode 100644
index a67aad2..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/DefaultObjectWrapperWithSortedMethods.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.beans;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-
-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(BeansWrapper bw, boolean desc) {
-        bw.setMethodSorter(new AlphabeticalMethodSorter(desc));
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/EnumModelsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/EnumModelsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/EnumModelsTest.java
deleted file mode 100644
index 90421c9..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/EnumModelsTest.java
+++ /dev/null
@@ -1,84 +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.beans;
-
-import static org.junit.Assert.*;
-
-import java.util.ArrayList;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class EnumModelsTest {
-    
-    @Test
-    public void modelCaching() throws Exception {
-        BeansWrapper bw = new BeansWrapper(Configuration.VERSION_3_0_0);
-        TemplateHashModel enums = bw.getEnumModels();
-        TemplateHashModel e = (TemplateHashModel) enums.get(E.class.getName());
-        assertNotNull(e);
-        assertNotNull(e.get("A"));
-        assertNotNull(e.get("B"));
-        assertNull(e.get("C"));
-
-        try {
-            enums.get("no.such.ClassExists");
-            fail();
-        } catch (TemplateModelException ex) {
-            assertTrue(ex.getCause() instanceof ClassNotFoundException);
-        }
-        
-        TemplateModel a = e.get("A");
-        assertTrue(a instanceof TemplateScalarModel);
-        assertTrue(a instanceof TemplateHashModel);
-        assertEquals(((TemplateScalarModel) a).getAsString(), "ts:A");
-        TemplateMethodModelEx nameMethod = (TemplateMethodModelEx) ((TemplateHashModel) a).get("name");
-        assertEquals(((TemplateScalarModel) nameMethod.exec(new ArrayList())).getAsString(), "A");
-        
-        assertSame(e, enums.get(E.class.getName()));
-        
-        bw.clearClassIntrospecitonCache();
-        TemplateHashModel eAfterClean = (TemplateHashModel) enums.get(E.class.getName());
-        assertNotSame(e, eAfterClean);
-        assertSame(eAfterClean, enums.get(E.class.getName()));
-        assertNotNull(eAfterClean.get("A"));
-        assertNotNull(eAfterClean.get("B"));
-        assertNull(eAfterClean.get("C"));
-    }
-    
-    public enum E {
-        A, B;
-
-        @Override
-        public String toString() {
-            return "ts:" + super.toString();
-        }
-        
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/ErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/ErrorMessagesTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/ErrorMessagesTest.java
deleted file mode 100644
index 61a2612..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/ErrorMessagesTest.java
+++ /dev/null
@@ -1,170 +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.beans;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.util.Collections;
-import java.util.Date;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
-import org.apache.freemarker.core.outputformat.impl.TemplateHTMLOutputModel;
-import org.junit.Test;
-
-public class ErrorMessagesTest {
-
-    @Test
-    public void getterMessage() throws TemplateModelException {
-        BeansWrapper bw = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-        TemplateHashModel thm= (TemplateHashModel) bw.wrap(new TestBean());
-        
-        try {
-            thm.get("foo");
-        } catch (TemplateModelException e) {
-            e.printStackTrace();
-            final String msg = e.getMessage();
-            assertThat(msg, containsString("\"foo\""));
-            assertThat(msg, containsString("existing sub-variable"));
-        }
-        assertNull(thm.get("bar"));
-    }
-    
-    @Test
-    public void markupOutputParameter() throws Exception {
-        TemplateHTMLOutputModel html = HTMLOutputFormat.INSTANCE.fromMarkup("<p>a");
-
-        BeansWrapper bw = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-        TemplateHashModel thm = (TemplateHashModel) bw.wrap(new TestBean());
-        
-        {
-            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get("m1");
-            try {
-                m.exec(Collections.singletonList(html));
-                fail();
-            } catch (TemplateModelException e) {
-                assertThat(e.getMessage(), allOf(
-                        containsString("String"), containsString("convert"), containsString("markup_output"),
-                        containsString("Tip:"), containsString("?markup_string")));
-            }
-        }
-        
-        {
-            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get("m2");
-            try {
-                m.exec(Collections.singletonList(html));
-                fail();
-            } catch (TemplateModelException e) {
-                assertThat(e.getMessage(), allOf(
-                        containsString("Date"), containsString("convert"), containsString("markup_output"),
-                        not(containsString("?markup_string"))));
-            }
-        }
-        
-        for (String methodName : new String[] { "mOverloaded", "mOverloaded3" }) {
-            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get(methodName);
-            try {
-                m.exec(Collections.singletonList(html));
-                fail();
-            } catch (TemplateModelException e) {
-                assertThat(e.getMessage(), allOf(
-                        containsString("No compatible overloaded"),
-                        containsString("String"), containsString("markup_output"),
-                        containsString("Tip:"), containsString("?markup_string")));
-            }
-        }
-        
-        {
-            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get("mOverloaded2");
-            try {
-                m.exec(Collections.singletonList(html));
-                fail();
-            } catch (TemplateModelException e) {
-                assertThat(e.getMessage(), allOf(
-                        containsString("No compatible overloaded"),
-                        containsString("Integer"), containsString("markup_output"),
-                        not(containsString("?markup_string"))));
-            }
-        }
-        
-        {
-            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get("mOverloaded4");
-            Object r = m.exec(Collections.singletonList(html));
-            if (r instanceof TemplateScalarModel) {
-                r = ((TemplateScalarModel) r).getAsString();
-            }
-            assertEquals("<p>a", r);
-        }
-    }
-    
-    public static class TestBean {
-        
-        public String getFoo() {
-            throw new RuntimeException("Dummy");
-        }
-        
-        public void m1(String s) {
-            // nop
-        }
-
-        public void m2(Date s) {
-            // nop
-        }
-
-        public void mOverloaded(String s) {
-            // nop
-        }
-
-        public void mOverloaded(Date d) {
-            // nop
-        }
-
-        public void mOverloaded2(Integer n) {
-            // nop
-        }
-
-        public void mOverloaded2(Date d) {
-            // nop
-        }
-
-        public void mOverloaded3(String... s) {
-            // nop
-        }
-
-        public void mOverloaded3(Date d) {
-            // nop
-        }
-        
-        public String mOverloaded4(String s) {
-            return s;
-        }
-
-        public String mOverloaded4(TemplateHTMLOutputModel s) throws TemplateModelException {
-            return s.getOutputFormat().getMarkupString(s);
-        }
-        
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/FineTuneMethodAppearanceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/FineTuneMethodAppearanceTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/FineTuneMethodAppearanceTest.java
deleted file mode 100644
index d21e116..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/FineTuneMethodAppearanceTest.java
+++ /dev/null
@@ -1,65 +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.beans;
-
-import static org.junit.Assert.*;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-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.DefaultObjectWrapper;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-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);
-        checkIfProperlyWrapped(ow.wrap(new C()));
-    }
-    
-    private void checkIfProperlyWrapped(TemplateModel tm) throws TemplateModelException {
-        TemplateHashModel thm = (TemplateHashModel) tm;
-        assertEquals("v1", ((TemplateScalarModel) thm.get("v1")).getAsString());
-        assertEquals("v2()", ((TemplateScalarModel) thm.get("v2")).getAsString());
-        assertEquals("getV3()", ((TemplateScalarModel) thm.get("v3")).getAsString());
-        assertTrue(thm.get("getV3") instanceof TemplateMethodModelEx);
-    }
-    
-    static public class C {
-        
-        public String v1 = "v1";
-
-        public String v2 = "v2";
-        public String v2() { return "v2()"; }
-        
-        public String v3() { return "v3()"; }
-        public String getV3() { return "getV3()"; }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/GetlessMethodsAsPropertyGettersRule.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/GetlessMethodsAsPropertyGettersRule.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/GetlessMethodsAsPropertyGettersRule.java
deleted file mode 100644
index 08a52e3..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/GetlessMethodsAsPropertyGettersRule.java
+++ /dev/null
@@ -1,70 +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.beans;
-
-import java.beans.IntrospectionException;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.Method;
-
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecision;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecisionInput;
-
-class GetlessMethodsAsPropertyGettersRule implements MethodAppearanceFineTuner, SingletonCustomizer {
-    
-    static final GetlessMethodsAsPropertyGettersRule INSTANCE = new GetlessMethodsAsPropertyGettersRule();
-    
-    // Can't be constructed from outside
-    private GetlessMethodsAsPropertyGettersRule() { }
-
-    @Override
-    public void process(
-            MethodAppearanceDecisionInput in, MethodAppearanceDecision out) {
-        legacyProcess(in.getContainingClass(), in.getMethod(), out);
-    }
-
-    /** This only exists as the tests need to call this through the deprecated method too. */
-    public void legacyProcess(
-            Class clazz, Method m, MethodAppearanceDecision decision) {
-        if (m.getDeclaringClass() != Object.class
-                && m.getReturnType() != void.class
-                && m.getParameterTypes().length == 0) {
-            String mName = m.getName();
-            if (!looksLikePropertyReadMethod(mName)) {
-                decision.setExposeMethodAs(null);
-                try {
-                    decision.setExposeAsProperty(new PropertyDescriptor(
-                            mName, clazz, mName, null));
-                } catch (IntrospectionException e) {  // Won't happen...
-                    throw new RuntimeException(e); 
-                }
-            }
-        }
-    }
-    
-    private static boolean looksLikePropertyReadMethod(String name) {
-        final int verbEnd;
-        if (name.startsWith("get")) verbEnd = 3;
-        else if (name.startsWith("is")) verbEnd = 2;
-        else return false;
-        
-        return name.length() == verbEnd || Character.isUpperCase(name.charAt(verbEnd));
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/IsApplicableTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/IsApplicableTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/IsApplicableTest.java
deleted file mode 100644
index 0bc9168..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/IsApplicableTest.java
+++ /dev/null
@@ -1,171 +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.beans;
-
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.TestCase;
-
-@SuppressWarnings("boxing")
-public class IsApplicableTest extends TestCase {
-
-    public IsApplicableTest(String name) {
-        super(name);
-    }
-    
-    public void testSingle() {
-        ArgumentTypes ats = new ArgumentTypes(new Object[] { new Object() });
-        assertApplicable(ats, Object.class);
-        assertNotApplicable(ats, String.class);
-        assertNotApplicable(ats, CharSequence.class);
-        assertNotApplicable(ats, Integer.class);
-        assertNotApplicable(ats, Integer.TYPE);
-        
-        ats = new ArgumentTypes(new Object[] { "" });
-        assertApplicable(ats, Object.class);
-        assertApplicable(ats, CharSequence.class);
-        assertApplicable(ats, String.class);
-        assertNotApplicable(ats, Integer.class);
-        assertNotApplicable(ats, Integer.TYPE);
-
-        ats = new ArgumentTypes(new Object[] { 1 });
-        assertApplicable(ats, Object.class);
-        assertNotApplicable(ats, CharSequence.class);
-        assertNotApplicable(ats, String.class);
-        assertNotApplicable(ats, Short.class);
-        assertNotApplicable(ats, Short.TYPE);
-        assertApplicable(ats, Integer.class);
-        assertApplicable(ats, Integer.TYPE);
-        assertApplicable(ats, Float.class);
-        assertApplicable(ats, Float.TYPE);
-        assertApplicable(ats, Double.class);
-        assertApplicable(ats, Double.TYPE);
-        assertApplicable(ats, BigDecimal.class);
-        assertApplicable(ats, BigInteger.class);
-
-        ats = new ArgumentTypes(new Object[] { new OverloadedNumberUtil.IntegerOrByte(1, (byte) 1) });
-        assertApplicable(ats, Object.class);
-        assertNotApplicable(ats, CharSequence.class);
-        assertNotApplicable(ats, String.class);
-        assertApplicable(ats, Short.class);
-        assertApplicable(ats, Short.TYPE);
-        assertApplicable(ats, Integer.class);
-        assertApplicable(ats, Integer.TYPE);
-        assertApplicable(ats, Float.class);
-        assertApplicable(ats, Float.TYPE);
-        assertApplicable(ats, Double.class);
-        assertApplicable(ats, Double.TYPE);
-        assertApplicable(ats, BigDecimal.class);
-        assertApplicable(ats, BigInteger.class);
-        
-        ats = new ArgumentTypes(new Object[] { 1.0f });
-        assertApplicable(ats, Object.class);
-        assertNotApplicable(ats, CharSequence.class);
-        assertNotApplicable(ats, String.class);
-        assertNotApplicable(ats, Integer.class);
-        assertNotApplicable(ats, Integer.TYPE);
-        assertApplicable(ats, Float.class);
-        assertApplicable(ats, Float.TYPE);
-        assertApplicable(ats, Double.class);
-        assertApplicable(ats, Double.TYPE);
-        assertApplicable(ats, BigDecimal.class);
-        assertNotApplicable(ats, BigInteger.class);
-        
-        ats = new ArgumentTypes(new Object[] { null });
-        assertApplicable(ats, Object.class);
-        assertApplicable(ats, String.class);
-        assertApplicable(ats, Integer.class);
-        assertNotApplicable(ats, Integer.TYPE);
-        assertNotApplicable(ats, Boolean.TYPE);
-        assertNotApplicable(ats, Object.class, Object.class);
-        assertNotApplicable(ats);
-    }
-    
-    public void testMulti() {
-        ArgumentTypes ats = new ArgumentTypes(new Object[] { new Object(), "", 1, true });
-        assertApplicable(ats, Object.class, Object.class, Object.class, Object.class);
-        assertApplicable(ats, Object.class, String.class, Number.class, Boolean.class);
-        assertApplicable(ats, Object.class, CharSequence.class, Integer.class, Serializable.class);
-        assertApplicable(ats, Object.class, Comparable.class, Integer.TYPE, Serializable.class);
-        assertNotApplicable(ats, Object.class, String.class, Number.class, Number.class);
-        assertNotApplicable(ats, Object.class, StringBuilder.class, Number.class, Boolean.class);
-        assertNotApplicable(ats, int.class, Object.class, Object.class, Object.class);
-        assertNotApplicable(ats, Object.class, Object.class, Object.class);
-        assertNotApplicable(ats, Object.class, Object.class, Object.class, Object.class, Object.class);
-    }    
-
-    public void testNoParam() {
-        ArgumentTypes ats = new ArgumentTypes(new Object[] { });
-        assertApplicable(ats);
-        assertNotApplicable(ats, Object.class);
-    }
-
-    public void testVarags() {
-        Object[][] argLists = new Object[][] {
-            new Object[] { "", 1, 2, 3 },
-            new Object[] { "", 1, (byte) 2, 3 },
-            new Object[] { "", 1},
-            new Object[] { "" },
-        };
-        for (Object[] args : argLists) {
-            ArgumentTypes ats = new ArgumentTypes(args);
-            assertApplicable(ats, true, String.class, int[].class);
-            assertApplicable(ats, true, String.class, Integer[].class);
-            assertApplicable(ats, true, Object.class, Comparable[].class);
-            assertApplicable(ats, true, Object.class, Object[].class);
-            assertNotApplicable(ats, true, StringBuilder.class, int[].class);
-            if (args.length > 1) {
-                assertNotApplicable(ats, true, String.class, String[].class);
-            } else {
-                assertApplicable(ats, true, String.class, String[].class);
-            }
-        }
-    }
-    
-    private void assertNotApplicable(ArgumentTypes ats, Class... paramTypes) {
-        assertNotApplicable(ats, false, paramTypes);
-    }
-    
-    private void assertNotApplicable(ArgumentTypes ats, boolean varargs, Class... paramTypes) {
-        List tested = new ArrayList();
-        tested.add(new ReflectionCallableMemberDescriptor((Method) null, paramTypes));
-        if (ats.getApplicables(tested, varargs).size() != 0) {
-            fail("Parameter types were applicable");
-        }
-    }
-
-    private void assertApplicable(ArgumentTypes ats, Class<?>... paramTypes) {
-        assertApplicable(ats, false, paramTypes);
-    }
-    
-    private void assertApplicable(ArgumentTypes ats, boolean varargs, Class<?>... paramTypes) {
-        List tested = new ArrayList();
-        tested.add(new ReflectionCallableMemberDescriptor((Method) null, paramTypes));
-        if (ats.getApplicables(tested, varargs).size() != 1) {
-            fail("Parameter types weren't applicable");
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/IsMoreSpecificParameterTypeTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/IsMoreSpecificParameterTypeTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/IsMoreSpecificParameterTypeTest.java
deleted file mode 100644
index e71f071..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/IsMoreSpecificParameterTypeTest.java
+++ /dev/null
@@ -1,98 +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.beans;
-
-import java.math.BigDecimal;
-import java.util.HashMap;
-import java.util.Map;
-
-import junit.framework.TestCase;
-
-public class IsMoreSpecificParameterTypeTest extends TestCase {
-
-    public IsMoreSpecificParameterTypeTest(String name) {
-        super(name);
-    }
-    
-    public void testFixed() {
-        assertEquals(1, _MethodUtil.isMoreOrSameSpecificParameterType(String.class, String.class, true, 0));
-        assertEquals(1, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, int.class, true, 0));
-        
-        assertEquals(2, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Integer.class, true, 0));
-        assertEquals(2, _MethodUtil.isMoreOrSameSpecificParameterType(boolean.class, Boolean.class, true, 0));
-        
-        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, long.class, true, 0));
-        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, double.class, true, 0));
-        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Long.class, true, 0));
-        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Double.class, true, 0));
-        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Long.class, true, 0));
-        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Double.class, true, 0));
-
-        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(HashMap.class, Map.class, true, 0));
-        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(String.class, CharSequence.class, true, 0));
-        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Number.class, true, 0));
-        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Number.class, true, 0));
-        
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Map.class, String.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, int.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Boolean.class, boolean.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, boolean.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Boolean.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, String.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, BigDecimal.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Long.class, Integer.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(long.class, Integer.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Long.class, int.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, BigDecimal.class, true, 0));
-    }
-    
-    public void testBuggy() {
-        assertEquals(1, _MethodUtil.isMoreOrSameSpecificParameterType(String.class, String.class, false, 0));
-        assertEquals(1, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, int.class, false, 0));
-        
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Integer.class, false, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(boolean.class, Boolean.class, false, 0));
-        
-        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, long.class, false, 0));
-        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, double.class, false, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Long.class, false, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Double.class, false, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Long.class, false, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Double.class, false, 0));
-
-        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(HashMap.class, Map.class, false, 0));
-        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(String.class, CharSequence.class, false, 0));
-        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Number.class, false, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Number.class, false, 0));
-        
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Map.class, String.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, int.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Boolean.class, boolean.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, boolean.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Boolean.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, String.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, BigDecimal.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Long.class, Integer.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(long.class, Integer.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Long.class, int.class, true, 0));
-        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, BigDecimal.class, true, 0));
-    }
-
-}


[13/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapper.java
deleted file mode 100644
index 2bc7902..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapper.java
+++ /dev/null
@@ -1,1644 +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.beans;
-
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.AccessibleObject;
-import java.lang.reflect.Array;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.ResourceBundle;
-import java.util.Set;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core._CoreAPI;
-import org.apache.freemarker.core._CoreLogs;
-import org.apache.freemarker.core._DelayedFTLTypeDescription;
-import org.apache.freemarker.core._DelayedShortClassName;
-import org.apache.freemarker.core._TemplateModelException;
-import org.apache.freemarker.core.model.AdapterTemplateModel;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
-import org.apache.freemarker.core.model.RichObjectWrapper;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateModelException;
-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.WrapperTemplateModel;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
-import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-import org.apache.freemarker.core.util.WriteProtectable;
-import org.apache.freemarker.core.util._ClassUtil;
-import org.slf4j.Logger;
-
-/**
- * {@link ObjectWrapper} that is able to expose the Java API of arbitrary Java objects. This is also the superclass of
- * {@link DefaultObjectWrapper}. Note that instances of this class generally should be created with a
- * {@link BeansWrapperBuilder}, not with its public constructors.
- * 
- * <p>
- * As of 2.3.22, using {@link BeansWrapper} unextended is not recommended. Instead, {@link DefaultObjectWrapper} with
- * its {@code incompatibleImprovements} property set to 2.3.22 (or higher) is the recommended {@link ObjectWrapper}.
- * 
- * <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. Using {@link BeansWrapperBuilder} also guarantees thread safety.
- */
-public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
-    
-    private static final Logger LOG = _CoreLogs.BEANS_WRAPPER;
-    
-    private static final Constructor<?> ENUMS_MODEL_CTOR = enumsModelCtor();
-    
-    /**
-     * At this level of exposure, all methods and properties of the
-     * wrapped objects are exposed to the template.
-     */
-    public static final int EXPOSE_ALL = 0;
-    
-    /**
-     * At this level of exposure, all methods and properties of the wrapped
-     * objects are exposed to the template except methods that are deemed
-     * not safe. The not safe methods are java.lang.Object methods wait() and
-     * notify(), java.lang.Class methods getClassLoader() and newInstance(),
-     * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and
-     * newInstance() methods, all java.lang.reflect.Field set methods, all 
-     * java.lang.Thread and java.lang.ThreadGroup methods that can change its 
-     * state, as well as the usual suspects in java.lang.System and
-     * java.lang.Runtime.
-     */
-    public static final int EXPOSE_SAFE = 1;
-    
-    /**
-     * At this level of exposure, only property getters are exposed.
-     * Additionally, property getters that map to unsafe methods are not
-     * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
-     */
-    public static final int EXPOSE_PROPERTIES_ONLY = 2;
-
-    /**
-     * 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.
-     */
-    public static final int EXPOSE_NOTHING = 3;
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Introspection cache:
-    
-    private final Object sharedIntrospectionLock;
-    
-    /** 
-     * {@link Class} to class info cache.
-     * This object is possibly shared with other {@link BeansWrapper}-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;
-    
-    /**
-     * {@link String} class name to {@link StaticModel} cache.
-     * This object only belongs to a single {@link BeansWrapper}.
-     * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good
-     * reference.
-     */
-    private final StaticModels staticModels;
-    
-    /**
-     * {@link String} class name to {@link EnumerationModel} cache.
-     * This object only belongs to a single {@link BeansWrapper}.
-     * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good
-     * reference.
-     */
-    private final ClassBasedModelFactory enumModels;
-    
-    /**
-     * Object to wrapped object cache; not used by default.
-     * This object only belongs to a single {@link BeansWrapper}.
-     */
-    private final ModelCache modelCache;
-
-    private final BooleanModel falseModel;
-    private final BooleanModel trueModel;
-    
-    // -----------------------------------------------------------------------------------------------------------------
-
-    // 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 simpleMapWrapper;  // initialized by PropertyAssignments.apply
-    private boolean strict;  // initialized by PropertyAssignments.apply
-    
-    private final Version incompatibleImprovements;
-    
-    /**
-     * Use {@link BeansWrapperBuilder} 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 BeansWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might returns
-     *   a lower version than what you have specified.
-     *
-     * @since 2.3.21
-     */
-    public BeansWrapper(Version incompatibleImprovements) {
-        this(new BeansWrapperConfiguration(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 #BeansWrapper(BeansWrapperConfiguration, boolean, boolean)} with {@code true}
-     * {@code finalizeConstruction} argument.
-     * 
-     * @since 2.3.21
-     */
-    protected BeansWrapper(BeansWrapperConfiguration bwConf, boolean writeProtected) {
-        this(bwConf, writeProtected, true);
-    }
-    
-    /**
-     * Initializes the instance based on the the {@link BeansWrapperConfiguration} 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.
-     *     
-     * @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
-     */
-    protected BeansWrapper(BeansWrapperConfiguration bwConf, boolean writeProtected, boolean finalizeConstruction) {
-        incompatibleImprovements = bwConf.getIncompatibleImprovements();  // normalized
-        
-        simpleMapWrapper = bwConf.isSimpleMapWrapper();
-        defaultDateType = bwConf.getDefaultDateType();
-        outerIdentity = bwConf.getOuterIdentity() != null ? bwConf.getOuterIdentity() : this;
-        strict = bwConf.isStrict();
-        
-        if (!writeProtected) {
-            // As this is not a read-only BeansWrapper, 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.classIntrospectorFactory, sharedIntrospectionLock);
-        } else {
-            // As this is a read-only BeansWrapper, the classIntrospector is never replaced, and since it's shared by
-            // other BeansWrapper instances, we use the lock belonging to the shared ClassIntrospector.
-            classIntrospector = bwConf.classIntrospectorFactory.build();
-            sharedIntrospectionLock = classIntrospector.getSharedLock(); 
-        }
-        
-        falseModel = new BooleanModel(Boolean.FALSE, this);
-        trueModel = new BooleanModel(Boolean.TRUE, this);
-        
-        staticModels = new StaticModels(BeansWrapper.this);
-        enumModels = createEnumModels(BeansWrapper.this);
-        modelCache = new BeansModelCache(BeansWrapper.this);
-        setUseModelCache(bwConf.getUseModelCache());
-
-        finalizeConstruction(writeProtected);
-    }
-
-    /**
-     * Meant to be called after {@link BeansWrapper#BeansWrapper(BeansWrapperConfiguration, boolean, 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();
-        }
-        
-        // Attention! At this point, the BeansWrapper must be fully initialized, as when the model factories are
-        // registered below, the BeansWrapper can immediately get concurrent callbacks. That those other threads will
-        // see consistent image of the BeansWrapper is ensured that callbacks are always sync-ed on
-        // classIntrospector.sharedLock, and so is classIntrospector.registerModelFactory(...).
-        
-        registerModelFactories();
-    }
-    
-    /**
-     * Makes the configuration properties (settings) of this {@link BeansWrapper} 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 BeansWrapperBuilder} 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)
-     */
-    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 BeansWrapper 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 BeansWrapper 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)
-     */
-    public ObjectWrapper getOuterIdentity() {
-        return outerIdentity;
-    }
-
-    /**
-     * When set to {@code true}, the keys in {@link Map}-s won't mix with the method names when looking at them
-     * from templates. The default is {@code false} for backward-compatibility, but is not recommended.
-     * 
-     * <p>When this is {@code false}, {@code myMap.foo} or {@code myMap['foo']} either returns the method {@code foo},
-     * or calls {@code Map.get("foo")}. If both exists (the method and the {@link Map} key), one will hide the other,
-     * depending on the {@link #isMethodsShadowItems()}, which default to {@code true} (the method
-     * wins). Some frameworks use this so that you can call {@code myMap.get(nonStringKey)} from templates [*], but it
-     * comes on the cost of polluting the key-set with the method names, and risking methods accidentally hiding
-     * {@link Map} entries (or the other way around). Thus, this setup is not recommended.
-     * (Technical note: {@link Map}-s will be wrapped into {@link MapModel} in this case.)  
-     *
-     * <p>When this is {@code true}, {@code myMap.foo} or {@code myMap['foo']} always calls {@code Map.get("foo")}.
-     * The methods of the {@link Map} object aren't visible from templates in this case. This, however, spoils the
-     * {@code myMap.get(nonStringKey)} workaround. But now you can use {@code myMap(nonStringKey)} instead, that is, you
-     * can use the map itself as the {@code get} method. 
-     * (Technical note: {@link Map}-s will be wrapped into {@link SimpleMapModel} in this case.)
-     * 
-     * <p>*: For historical reasons, FreeMarker 2.3.X doesn't support non-string keys with the {@code []} operator,
-     *       hence the workarounds. This will be likely oms in FreeMarker 2.4.0. Also note that the method- and
-     *       the "field"-namespaces aren't separate in FreeMarker, hence {@code myMap.get} can return the {@code get}
-     *       method.
-     */
-    public void setSimpleMapWrapper(boolean simpleMapWrapper) {
-        checkModifiable();
-        this.simpleMapWrapper = simpleMapWrapper;
-    }
-
-    /**
-     * Tells whether Maps are exposed as simple maps, without access to their
-     * method. See {@link #setSimpleMapWrapper(boolean)} for details.
-     * @return true if Maps are exposed as simple hashes, false if they're
-     * exposed as full JavaBeans.
-     */
-    public boolean isSimpleMapWrapper() {
-        return simpleMapWrapper;
-    }
-
-    // I have commented this out, as it won't be in 2.3.20 yet.
-    /*
-    /**
-     * Tells which non-backward-compatible overloaded method selection fixes to apply;
-     * see {@link #setOverloadedMethodSelection(Version)}.
-     * /
-    public Version getOverloadedMethodSelection() {
-        return overloadedMethodSelection;
-    }
-
-    /**
-     * Sets which non-backward-compatible overloaded method selection fixes to apply.
-     * This has similar logic as {@link Configuration#setIncompatibleImprovements(Version)},
-     * but only applies to this aspect.
-     * 
-     * Currently significant values:
-     * <ul>
-     *   <li>2.3.21: Completetlly rewritten overloaded method selection, fixes several issues with the old one.</li>
-     * </ul>
-     * /
-    public void setOverloadedMethodSelection(Version version) {
-        overloadedMethodSelection = version;
-    }
-    */
-    
-    /**
-     * 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 pa = classIntrospector.getPropertyAssignments();
-            pa.setExposureLevel(exposureLevel);
-            replaceClassIntrospector(pa);
-        }
-    }
-    
-    /**
-     * @since 2.3.21
-     */
-    public int getExposureLevel() {
-        return classIntrospector.getExposureLevel();
-    }
-    
-    /**
-     * 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 pa = classIntrospector.getPropertyAssignments();
-            pa.setExposeFields(exposeFields);
-            replaceClassIntrospector(pa);
-        }
-    }
-    
-    /**
-     * Returns whether exposure of public instance fields of classes is 
-     * enabled. See {@link #setExposeFields(boolean)} for details.
-     * @return true if public instance fields are exposed, false otherwise.
-     */
-    public boolean isExposeFields() {
-        return classIntrospector.getExposeFields();
-    }
-    
-    public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
-        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 pa = classIntrospector.getPropertyAssignments();
-            pa.setMethodAppearanceFineTuner(methodAppearanceFineTuner);
-            replaceClassIntrospector(pa);
-        }
-    }
-
-    MethodSorter getMethodSorter() {
-        return classIntrospector.getMethodSorter();
-    }
-
-    void setMethodSorter(MethodSorter methodSorter) {
-        checkModifiable();
-        
-        if (classIntrospector.getMethodSorter() != methodSorter) {
-            ClassIntrospectorBuilder pa = classIntrospector.getPropertyAssignments();
-            pa.setMethodSorter(methodSorter);
-            replaceClassIntrospector(pa);
-        }
-    }
-    
-    /**
-     * Tells if this instance acts like if its class introspection cache is sharable with other {@link BeansWrapper}-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 BeansWrapperBuilder}
-     * (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. 
-     * 
-     * @since 2.3.21
-     */
-    public boolean isClassIntrospectionCacheRestricted() {
-        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 pa) {
-        checkModifiable();
-        
-        final ClassIntrospector newCI = new ClassIntrospector(pa, 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();
-                }
-                if (modelCache != null) {
-                    oldCI.unregisterModelFactory(modelCache);
-                    modelCache.clearCache();
-                }
-                if (trueModel != null) {
-                    trueModel.clearMemberCache();
-                }
-                if (falseModel != null) {
-                    falseModel.clearMemberCache();
-                }
-            }
-            
-            classIntrospector = newCI;
-            
-            registerModelFactories();
-        }
-    }
-
-    private void registerModelFactories() {
-        if (staticModels != null) {
-            classIntrospector.registerModelFactory(staticModels);
-        }
-        if (enumModels != null) {
-            classIntrospector.registerModelFactory(enumModels);
-        }
-        if (modelCache != null) {
-            classIntrospector.registerModelFactory(modelCache);
-        }
-    }
-
-    /**
-     * 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
-     * details.
-     * @return the default date type
-     */
-    public int getDefaultDateType() {
-        return defaultDateType;
-    }
-    
-    /**
-     * Sets whether this wrapper caches the {@link TemplateModel}-s created for the Java objects that has wrapped with
-     * this object wrapper. Default is {@code false}.
-     * When set to {@code true}, calling {@link #wrap(Object)} multiple times for
-     * the same object will likely return the same model (although there is
-     * no guarantee as the cache items can be cleared any time).
-     */
-    public void setUseModelCache(boolean useCache) {
-        checkModifiable();
-        modelCache.setUseCache(useCache);
-    }
-
-    /**
-     * @since 2.3.21
-     */
-    public boolean getUseModelCache() {
-        return modelCache.getUseCache();
-    }
-    
-    /**
-     * Returns the version given with {@link #BeansWrapper(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
-     */
-    public Version getIncompatibleImprovements() {
-        return incompatibleImprovements;
-    }
-    
-    /** 
-     * Returns the lowest version number that is equivalent with the parameter version.
-     * @since 2.3.21
-     */
-    protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
-        _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
-        return Configuration.VERSION_3_0_0;
-    }
-
-    /**
-     * Wraps the object with a template model that is most specific for the object's
-     * class. Specifically:
-     * <ul>
-     * <li>if the object is a Number returns a {@link NumberModel} for it,</li>
-     * <li>if the object is a Date returns a {@link DateModel} for it,</li>
-     * <li>if the object is a Boolean returns 
-     * {@link org.apache.freemarker.core.model.TemplateBooleanModel#TRUE} or 
-     * {@link org.apache.freemarker.core.model.TemplateBooleanModel#FALSE}</li>
-     * <li>if the object is already a TemplateModel, returns it unchanged,</li>
-     * <li>if the object is an array, returns a {@link ArrayModel} for it
-     * <li>if the object is a Map, returns a {@link MapModel} for it
-     * <li>if the object is a Collection, returns a {@link CollectionModel} for it
-     * <li>if the object is an Iterator, returns a {@link IteratorModel} for it
-     * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it
-     * <li>if the object is a String, returns a {@link StringModel} for it
-     * <li>otherwise, returns a generic {@link StringModel} for it.
-     * </ul>
-     */
-    @Override
-    public TemplateModel wrap(Object object) throws TemplateModelException {
-        if (object == null) return null;
-        return modelCache.getInstance(object);
-    }
-    
-    /**
-     * Wraps a Java method so that it can be called from templates, without wrapping its parent ("this") object. The
-     * result is almost the same as that you would get by wrapping the parent object then getting the method from the
-     * resulting {@link TemplateHashModel} by name. Except, if the wrapped method is overloaded, with this method you
-     * explicitly select a an overload, while otherwise you would get a {@link TemplateMethodModelEx} that selects an
-     * overload each time it's called based on the argument values.
-     * 
-     * @param object The object whose method will be called, or {@code null} if {@code method} is a static method.
-     *          This object will be used "as is", like without unwrapping it if it's a {@link TemplateModelAdapter}.
-     * @param method The method to call, which must be an (inherited) member of the class of {@code object}, as
-     *          described by {@link Method#invoke(Object, Object...)}
-     * 
-     * @since 2.3.22
-     */
-    public TemplateMethodModelEx wrap(Object object, Method method) {
-        return new SimpleMethodModel(object, method, method.getParameterTypes(), this);
-    }
-    
-    /**
-     * @since 2.3.22
-     */
-    @Override
-    public TemplateHashModel wrapAsAPI(Object obj) throws TemplateModelException {
-        return new APIModel(obj, this);
-    }
-
-    private final ModelFactory BOOLEAN_FACTORY = new ModelFactory() {
-        @Override
-        public TemplateModel create(Object object, ObjectWrapper wrapper) {
-            return ((Boolean) object).booleanValue() ? trueModel : falseModel; 
-        }
-    };
-
-    private static final ModelFactory ITERATOR_FACTORY = new ModelFactory() {
-        @Override
-        public TemplateModel create(Object object, ObjectWrapper wrapper) {
-            return new IteratorModel((Iterator<?>) object, (BeansWrapper) wrapper); 
-        }
-    };
-
-    private static final ModelFactory ENUMERATION_FACTORY = new ModelFactory() {
-        @Override
-        public TemplateModel create(Object object, ObjectWrapper wrapper) {
-            return new EnumerationModel((Enumeration<?>) object, (BeansWrapper) wrapper); 
-        }
-    };
-
-    protected ModelFactory getModelFactory(Class<?> clazz) {
-        if (Map.class.isAssignableFrom(clazz)) {
-            return simpleMapWrapper ? SimpleMapModel.FACTORY : MapModel.FACTORY;
-        }
-        if (Collection.class.isAssignableFrom(clazz)) {
-            return CollectionModel.FACTORY;
-        }
-        if (Number.class.isAssignableFrom(clazz)) {
-            return NumberModel.FACTORY;
-        }
-        if (Date.class.isAssignableFrom(clazz)) {
-            return DateModel.FACTORY;
-        }
-        if (Boolean.class == clazz) { // Boolean is final 
-            return BOOLEAN_FACTORY;
-        }
-        if (ResourceBundle.class.isAssignableFrom(clazz)) {
-            return ResourceBundleModel.FACTORY;
-        }
-        if (Iterator.class.isAssignableFrom(clazz)) {
-            return ITERATOR_FACTORY;
-        }
-        if (Enumeration.class.isAssignableFrom(clazz)) {
-            return ENUMERATION_FACTORY;
-        }
-        if (clazz.isArray()) {
-            return ArrayModel.FACTORY;
-        }
-        return StringModel.FACTORY;
-    }
-
-    /**
-     * Attempts to unwrap a model into underlying object. Generally, this
-     * method is the inverse of the {@link #wrap(Object)} method. In addition
-     * it will unwrap arbitrary {@link TemplateNumberModel} instances into
-     * a number, arbitrary {@link TemplateDateModel} instances into a date,
-     * {@link TemplateScalarModel} instances into a String, arbitrary 
-     * {@link TemplateBooleanModel} instances into a Boolean, arbitrary 
-     * {@link TemplateHashModel} instances into a Map, arbitrary 
-     * {@link TemplateSequenceModel} into a List, and arbitrary 
-     * {@link TemplateCollectionModel} into a Set. All other objects are 
-     * returned unchanged.
-     * @throws TemplateModelException if an attempted unwrapping fails.
-     */
-    @Override
-    public Object unwrap(TemplateModel model) throws TemplateModelException {
-        return unwrap(model, Object.class);
-    }
-
-    /**
-     * Attempts to unwrap a model into an object of the desired class. 
-     * Generally, this method is the inverse of the {@link #wrap(Object)} 
-     * method. It recognizes a wide range of target classes - all Java built-in
-     * primitives, primitive wrappers, numbers, dates, sets, lists, maps, and
-     * native arrays.
-     * @param model the model to unwrap
-     * @param targetClass the class of the unwrapped result; {@code Object.class} of we don't know what the expected type is.
-     * @return the unwrapped result of the desired class
-     * @throws TemplateModelException if an attempted unwrapping fails.
-     * 
-     * @see #tryUnwrapTo(TemplateModel, Class)
-     */
-    public Object unwrap(TemplateModel model, Class<?> targetClass) 
-    throws TemplateModelException {
-        final Object obj = tryUnwrapTo(model, targetClass);
-        if (obj == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-          throw new TemplateModelException("Can not unwrap model of type " + 
-              model.getClass().getName() + " to type " + targetClass.getName());
-        }
-        return obj;
-    }
-
-    /**
-     * @since 2.3.22
-     */
-    @Override
-    public Object tryUnwrapTo(TemplateModel model, Class<?> targetClass) throws TemplateModelException {
-        return tryUnwrapTo(model, targetClass, 0);
-    }
-    
-    /**
-     * @param typeFlags
-     *            Used when unwrapping for overloaded methods and so the {@code targetClass} is possibly too generic
-     *            (as it's the most specific common superclass). Must be 0 when unwrapping parameter values for
-     *            non-overloaded methods.
-     * @return {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} or the unwrapped object.
-     */
-    Object tryUnwrapTo(TemplateModel model, Class<?> targetClass, int typeFlags) throws TemplateModelException {
-        Object res = tryUnwrapTo(model, targetClass, typeFlags, null);
-        if ((typeFlags & TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT) != 0
-                && res instanceof Number) {
-            return OverloadedNumberUtil.addFallbackType((Number) res, typeFlags);
-        } else {
-            return res;
-        }
-    }
-
-    /**
-     * See {@link #tryUnwrapTo(TemplateModel, Class, int)}.
-     */
-    private Object tryUnwrapTo(final TemplateModel model, Class<?> targetClass, final int typeFlags,
-            final Map<Object, Object> recursionStops) 
-    throws TemplateModelException {
-        if (model == null) {
-            return null;
-        }
-        
-        if (targetClass.isPrimitive()) {
-            targetClass = _ClassUtil.primitiveClassToBoxingClass(targetClass);
-        }
-        
-        // This is for transparent interop with other wrappers (and ourselves)
-        // Passing the targetClass allows e.g. a Jython-aware method that declares a
-        // PyObject as its argument to receive a PyObject from a Jython-aware TemplateModel
-        // passed as an argument to TemplateMethodModelEx etc.
-        if (model instanceof AdapterTemplateModel) {
-            Object wrapped = ((AdapterTemplateModel) model).getAdaptedObject(targetClass);
-            if (targetClass == Object.class || targetClass.isInstance(wrapped)) {
-                return wrapped;
-            }
-            
-            // Attempt numeric conversion: 
-            if (targetClass != Object.class && (wrapped instanceof Number && _ClassUtil.isNumerical(targetClass))) {
-                Number number = forceUnwrappedNumberToType((Number) wrapped, targetClass);
-                if (number != null) return number;
-            }
-        }
-        
-        if (model instanceof WrapperTemplateModel) {
-            Object wrapped = ((WrapperTemplateModel) model).getWrappedObject();
-            if (targetClass == Object.class || targetClass.isInstance(wrapped)) {
-                return wrapped;
-            }
-            
-            // Attempt numeric conversion: 
-            if (targetClass != Object.class && (wrapped instanceof Number && _ClassUtil.isNumerical(targetClass))) {
-                Number number = forceUnwrappedNumberToType((Number) wrapped, targetClass);
-                if (number != null) {
-                    return number;
-                }
-            }
-        }
-        
-        // Translation of generic template models to POJOs. First give priority
-        // to various model interfaces based on the targetClass. This helps us
-        // select the appropriate interface in multi-interface models when we
-        // know what is expected as the return type.
-        if (targetClass != Object.class) {
-
-            // [2.4][IcI]: Should also check for CharSequence at the end
-            if (String.class == targetClass) {
-                if (model instanceof TemplateScalarModel) {
-                    return ((TemplateScalarModel) model).getAsString();
-                }
-                // String is final, so no other conversion will work
-                return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
-            }
-    
-            // Primitive numeric types & Number.class and its subclasses
-            if (_ClassUtil.isNumerical(targetClass)) {
-                if (model instanceof TemplateNumberModel) {
-                    Number number = forceUnwrappedNumberToType(
-                            ((TemplateNumberModel) model).getAsNumber(), targetClass);
-                    if (number != null) {
-                        return number;
-                    }
-                }
-            }
-            
-            if (boolean.class == targetClass || Boolean.class == targetClass) {
-                if (model instanceof TemplateBooleanModel) {
-                    return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean());
-                }
-                // Boolean is final, no other conversion will work
-                return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
-            }
-    
-            if (Map.class == targetClass) {
-                if (model instanceof TemplateHashModel) {
-                    return new HashAdapter((TemplateHashModel) model, this);
-                }
-            }
-            
-            if (List.class == targetClass) {
-                if (model instanceof TemplateSequenceModel) {
-                    return new SequenceAdapter((TemplateSequenceModel) model, this);
-                }
-            }
-            
-            if (Set.class == targetClass) {
-                if (model instanceof TemplateCollectionModel) {
-                    return new SetAdapter((TemplateCollectionModel) model, this);
-                }
-            }
-            
-            if (Collection.class == targetClass || Iterable.class == targetClass) {
-                if (model instanceof TemplateCollectionModel) {
-                    return new CollectionAdapter((TemplateCollectionModel) model, 
-                            this);
-                }
-                if (model instanceof TemplateSequenceModel) {
-                    return new SequenceAdapter((TemplateSequenceModel) model, this);
-                }
-            }
-            
-            // TemplateSequenceModels can be converted to arrays
-            if (targetClass.isArray()) {
-                if (model instanceof TemplateSequenceModel) {
-                    return unwrapSequenceToArray((TemplateSequenceModel) model, targetClass, true, recursionStops);
-                }
-                // array classes are final, no other conversion will work
-                return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
-            }
-            
-            // Allow one-char strings to be coerced to characters
-            if (char.class == targetClass || targetClass == Character.class) {
-                if (model instanceof TemplateScalarModel) {
-                    String s = ((TemplateScalarModel) model).getAsString();
-                    if (s.length() == 1) {
-                        return Character.valueOf(s.charAt(0));
-                    }
-                }
-                // Character is final, no other conversion will work
-                return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
-            }
-    
-            if (Date.class.isAssignableFrom(targetClass) && model instanceof TemplateDateModel) {
-                Date date = ((TemplateDateModel) model).getAsDate();
-                if (targetClass.isInstance(date)) {
-                    return date;
-                }
-            }
-        }  //  End: if (targetClass != Object.class)
-        
-        // Since the targetClass was of no help initially, now we use
-        // a quite arbitrary order in which we walk through the TemplateModel subinterfaces, and unwrapp them to
-        // their "natural" Java correspondent. We still try exclude unwrappings that won't fit the target parameter
-        // type(s). This is mostly important because of multi-typed FTL values that could be unwrapped on multiple ways.
-        int itf = typeFlags; // Iteration's Type Flags. Should be always 0 for non-overloaded and when !is2321Bugfixed.
-        // If itf != 0, we possibly execute the following loop body at twice: once with utilizing itf, and if it has not
-        // returned, once more with itf == 0. Otherwise we execute this once with itf == 0.
-        do {
-            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_NUMBER) != 0)
-                    && model instanceof TemplateNumberModel) {
-                Number number = ((TemplateNumberModel) model).getAsNumber();
-                if (itf != 0 || targetClass.isInstance(number)) {
-                    return number;
-                }
-            }
-            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_DATE) != 0)
-                    && model instanceof TemplateDateModel) {
-                Date date = ((TemplateDateModel) model).getAsDate();
-                if (itf != 0 || targetClass.isInstance(date)) {
-                    return date;
-                }
-            }
-            if ((itf == 0 || (itf & (TypeFlags.ACCEPTS_STRING | TypeFlags.CHARACTER)) != 0)
-                    && model instanceof TemplateScalarModel
-                    && (itf != 0 || targetClass.isAssignableFrom(String.class))) {
-                String strVal = ((TemplateScalarModel) model).getAsString();
-                if (itf == 0 || (itf & TypeFlags.CHARACTER) == 0) {
-                    return strVal;
-                } else { // TypeFlags.CHAR == 1
-                    if (strVal.length() == 1) {
-                        if ((itf & TypeFlags.ACCEPTS_STRING) != 0) {
-                            return new CharacterOrString(strVal);
-                        } else {
-                            return Character.valueOf(strVal.charAt(0));
-                        }
-                    } else if ((itf & TypeFlags.ACCEPTS_STRING) != 0) {
-                        return strVal; 
-                    }
-                    // It had to be unwrapped to Character, but the string length wasn't 1 => Fall through
-                }
-            }
-            // Should be earlier than TemplateScalarModel, but we keep it here until FM 2.4 or such
-            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_BOOLEAN) != 0)
-                    && model instanceof TemplateBooleanModel
-                    && (itf != 0 || targetClass.isAssignableFrom(Boolean.class))) {
-                return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean());
-            }
-            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_MAP) != 0)
-                    && model instanceof TemplateHashModel
-                    && (itf != 0 || targetClass.isAssignableFrom(HashAdapter.class))) {
-                return new HashAdapter((TemplateHashModel) model, this);
-            }
-            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_LIST) != 0)
-                    && model instanceof TemplateSequenceModel 
-                    && (itf != 0 || targetClass.isAssignableFrom(SequenceAdapter.class))) {
-                return new SequenceAdapter((TemplateSequenceModel) model, this);
-            }
-            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_SET) != 0)
-                    && model instanceof TemplateCollectionModel
-                    && (itf != 0 || targetClass.isAssignableFrom(SetAdapter.class))) {
-                return new SetAdapter((TemplateCollectionModel) model, this);
-            }
-            
-            if ((itf & TypeFlags.ACCEPTS_ARRAY) != 0
-                    && model instanceof TemplateSequenceModel) {
-                return new SequenceAdapter((TemplateSequenceModel) model, this);
-            }
-            
-            if (itf == 0) {
-                break;
-            }
-            itf = 0; // start 2nd iteration
-        } while (true);
-
-        // Last ditch effort - is maybe the model itself is an instance of the required type?
-        // Note that this will be always true for Object.class targetClass. 
-        if (targetClass.isInstance(model)) {
-            return model;
-        }
-        
-        return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
-    }
-
-    /**
-     * @param tryOnly
-     *            If {@code true}, if the conversion of an item to the component type isn't possible, the method returns
-     *            {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} instead of throwing a
-     *            {@link TemplateModelException}.
-     */
-    Object unwrapSequenceToArray(
-            TemplateSequenceModel seq, Class<?> arrayClass, boolean tryOnly, Map<Object, Object> recursionStops)
-            throws TemplateModelException {
-        if (recursionStops != null) {
-            Object retval = recursionStops.get(seq);
-            if (retval != null) {
-                return retval;
-            }
-        } else {
-            recursionStops = new IdentityHashMap<>();
-        }
-        Class<?> componentType = arrayClass.getComponentType();
-        Object array = Array.newInstance(componentType, seq.size());
-        recursionStops.put(seq, array);
-        try {
-            final int size = seq.size();
-            for (int i = 0; i < size; i++) {
-                final TemplateModel seqItem = seq.get(i);
-                Object val = tryUnwrapTo(seqItem, componentType, 0, recursionStops);
-                if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-                    if (tryOnly) {
-                        return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
-                    } else {
-                        throw new _TemplateModelException(
-                                "Failed to convert ",  new _DelayedFTLTypeDescription(seq),
-                                " object to ", new _DelayedShortClassName(array.getClass()),
-                                ": Problematic sequence item at index ", Integer.valueOf(i) ," with value type: ",
-                                new _DelayedFTLTypeDescription(seqItem));
-                    }
-                    
-                }
-                Array.set(array, i, val);
-            }
-        } finally {
-            recursionStops.remove(seq);
-        }
-        return array;
-    }
-    
-    Object listToArray(List<?> list, Class<?> arrayClass, Map<Object, Object> recursionStops)
-            throws TemplateModelException {
-        if (list instanceof SequenceAdapter) {
-            return unwrapSequenceToArray(
-                    ((SequenceAdapter) list).getTemplateSequenceModel(),
-                    arrayClass, false,
-                    recursionStops);
-        }
-        
-        if (recursionStops != null) {
-            Object retval = recursionStops.get(list);
-            if (retval != null) {
-                return retval;
-            }
-        } else {
-            recursionStops = new IdentityHashMap<>();
-        }
-        Class<?> componentType = arrayClass.getComponentType();
-        Object array = Array.newInstance(componentType, list.size());
-        recursionStops.put(list, array);
-        try {
-            boolean isComponentTypeExamined = false;
-            boolean isComponentTypeNumerical = false;  // will be filled on demand
-            boolean isComponentTypeList = false;  // will be filled on demand
-            int i = 0;
-            for (Object listItem : list) {
-                if (listItem != null && !componentType.isInstance(listItem)) {
-                    // Type conversion is needed. If we can't do it, we just let it fail at Array.set later.
-                    if (!isComponentTypeExamined) {
-                        isComponentTypeNumerical = _ClassUtil.isNumerical(componentType);
-                        isComponentTypeList = List.class.isAssignableFrom(componentType);
-                        isComponentTypeExamined = true;
-                    }
-                    if (isComponentTypeNumerical && listItem instanceof Number) {
-                        listItem = forceUnwrappedNumberToType((Number) listItem, componentType);
-                    } else if (componentType == String.class && listItem instanceof Character) {
-                        listItem = String.valueOf(((Character) listItem).charValue());
-                    } else if ((componentType == Character.class || componentType == char.class)
-                            && listItem instanceof String) {
-                        String listItemStr = (String) listItem;
-                        if (listItemStr.length() == 1) {
-                            listItem = Character.valueOf(listItemStr.charAt(0));
-                        }
-                    } else if (componentType.isArray()) {
-                        if (listItem instanceof List) {
-                            listItem = listToArray((List<?>) listItem, componentType, recursionStops);
-                        } else if (listItem instanceof TemplateSequenceModel) {
-                            listItem = unwrapSequenceToArray((TemplateSequenceModel) listItem, componentType, false, recursionStops);
-                        }
-                    } else if (isComponentTypeList && listItem.getClass().isArray()) {
-                        listItem = arrayToList(listItem);
-                    }
-                }
-                try {
-                    Array.set(array, i, listItem);
-                } catch (IllegalArgumentException e) {
-                    throw new TemplateModelException(
-                            "Failed to convert " + _ClassUtil.getShortClassNameOfObject(list)
-                            + " object to " + _ClassUtil.getShortClassNameOfObject(array)
-                            + ": Problematic List item at index " + i + " with value type: "
-                            + _ClassUtil.getShortClassNameOfObject(listItem), e);
-                }
-                i++;
-            }
-        } finally {
-            recursionStops.remove(list);
-        }
-        return array;
-    }
-    
-    /**
-     * @param array Must be an array (of either a reference or primitive type)
-     */
-    List<?> arrayToList(Object array) throws TemplateModelException {
-        if (array instanceof Object[]) {
-            // Array of any non-primitive type.
-            // Note that an array of non-primitive type is always instanceof Object[].
-            Object[] objArray = (Object[]) array;
-            return objArray.length == 0 ? Collections.EMPTY_LIST : new NonPrimitiveArrayBackedReadOnlyList(objArray);
-        } else {
-            // Array of any primitive type
-            return Array.getLength(array) == 0 ? Collections.EMPTY_LIST : new PrimtiveArrayBackedReadOnlyList(array);
-        }
-    }
-
-    /**
-     * Converts a number to the target type aggressively (possibly with overflow or significant loss of precision).
-     * @param n Non-{@code null}
-     * @return {@code null} if the conversion has failed.
-     */
-    static Number forceUnwrappedNumberToType(final Number n, final Class<?> targetType) {
-        // We try to order the conditions by decreasing probability.
-        if (targetType == n.getClass()) {
-            return n;
-        } else if (targetType == int.class || targetType == Integer.class) {
-            return n instanceof Integer ? (Integer) n : Integer.valueOf(n.intValue());
-        } else if (targetType == long.class || targetType == Long.class) {
-            return n instanceof Long ? (Long) n : Long.valueOf(n.longValue());
-        } else if (targetType == double.class || targetType == Double.class) {
-            return n instanceof Double ? (Double) n : Double.valueOf(n.doubleValue());
-        } else if (targetType == BigDecimal.class) {
-            if (n instanceof BigDecimal) {
-                return n;
-            } else if (n instanceof BigInteger) {
-                return new BigDecimal((BigInteger) n);
-            } else if (n instanceof Long) {
-                // Because we can't represent long accurately as double
-                return BigDecimal.valueOf(n.longValue());
-            } else {
-                return new BigDecimal(n.doubleValue());
-            }
-        } else if (targetType == float.class || targetType == Float.class) {
-            return n instanceof Float ? (Float) n : Float.valueOf(n.floatValue());
-        } else if (targetType == byte.class || targetType == Byte.class) {
-            return n instanceof Byte ? (Byte) n : Byte.valueOf(n.byteValue());
-        } else if (targetType == short.class || targetType == Short.class) {
-            return n instanceof Short ? (Short) n : Short.valueOf(n.shortValue());
-        } else if (targetType == BigInteger.class) {
-            if (n instanceof BigInteger) {
-                return n;
-            } else {
-                if (n instanceof OverloadedNumberUtil.IntegerBigDecimal) {
-                    return ((OverloadedNumberUtil.IntegerBigDecimal) n).bigIntegerValue();
-                } else if (n instanceof BigDecimal) {
-                    return ((BigDecimal) n).toBigInteger(); 
-                } else {
-                    return BigInteger.valueOf(n.longValue()); 
-                }
-            }
-        } else {
-            final Number oriN = n instanceof OverloadedNumberUtil.NumberWithFallbackType
-                    ? ((OverloadedNumberUtil.NumberWithFallbackType) n).getSourceNumber() : n; 
-            if (targetType.isInstance(oriN)) {
-                // Handle nonstandard Number subclasses as well as directly java.lang.Number.
-                return oriN;
-            } else {
-                // Fails
-                return null;
-            }
-        }
-    }
-    
-    /**
-     * Invokes the specified method, wrapping the return value. The specialty
-     * of this method is that if the return value is null, and the return type
-     * of the invoked method is void, {@link TemplateModel#NOTHING} is returned.
-     * @param object the object to invoke the method on
-     * @param method the method to invoke 
-     * @param args the arguments to the method
-     * @return the wrapped return value of the method.
-     * @throws InvocationTargetException if the invoked method threw an exception
-     * @throws IllegalAccessException if the method can't be invoked due to an
-     * access restriction. 
-     * @throws TemplateModelException if the return value couldn't be wrapped
-     * (this can happen if the wrapper has an outer identity or is subclassed,
-     * and the outer identity or the subclass throws an exception. Plain
-     * BeansWrapper never throws TemplateModelException).
-     */
-    TemplateModel invokeMethod(Object object, Method method, Object[] args)
-    throws InvocationTargetException,
-        IllegalAccessException,
-        TemplateModelException {
-        // [2.4]: Java's Method.invoke truncates numbers if the target type has not enough bits to hold the value.
-        // There should at least be an option to check this.
-        Object retval = method.invoke(object, args);
-        return 
-            method.getReturnType() == void.class 
-            ? TemplateModel.NOTHING
-            : getOuterIdentity().wrap(retval); 
-    }
-
-   /**
-     * Returns a hash model that represents the so-called class static models.
-     * Every class static model is itself a hash through which you can call
-     * static methods on the specified class. To obtain a static model for a
-     * class, get the element of this hash with the fully qualified class name.
-     * For example, if you place this hash model inside the root data model
-     * under name "statics", you can use i.e. <code>statics["java.lang.
-     * System"]. currentTimeMillis()</code> to call the {@link 
-     * java.lang.System#currentTimeMillis()} method.
-     * @return a hash model whose keys are fully qualified class names, and
-     * that returns hash models whose elements are the static models of the
-     * classes.
-     */
-    public TemplateHashModel getStaticModels() {
-        return staticModels;
-    }
-
-    /**
-     * Returns a hash model that represents the so-called class enum models.
-     * Every class' enum model is itself a hash through which you can access
-     * enum value declared by the specified class, assuming that class is an
-     * enumeration. To obtain an enum model for a class, get the element of this
-     * hash with the fully qualified class name. For example, if you place this 
-     * hash model inside the root data model under name "enums", you can use 
-     * i.e. <code>statics["java.math.RoundingMode"].UP</code> to access the 
-     * {@link java.math.RoundingMode#UP} value.
-     * @return a hash model whose keys are fully qualified class names, and
-     * that returns hash models whose elements are the enum models of the
-     * classes.
-     * @throws UnsupportedOperationException if this method is invoked on a 
-     * pre-1.5 JRE, as Java enums aren't supported there.
-     */
-    public TemplateHashModel getEnumModels() {
-        if (enumModels == null) {
-            throw new UnsupportedOperationException(
-                    "Enums not supported before J2SE 5.");
-        }
-        return enumModels;
-    }
-    
-    /** For Unit tests only */
-    ModelCache getModelCache() {
-        return modelCache;
-    }
-
-    /**
-     * Creates a new instance of the specified class using the method call logic of this object wrapper for calling the
-     * constructor. Overloaded constructors and varargs are supported. Only public constructors will be called.
-     * 
-     * @param clazz The class whose constructor we will call.
-     * @param arguments The list of {@link TemplateModel}-s to pass to the constructor after unwrapping them
-     * @return The instance created; it's not wrapped into {@link TemplateModel}.
-     */
-    public Object newInstance(Class<?> clazz, List/*<? extends TemplateModel>*/ arguments)
-    throws TemplateModelException {
-        try {
-            Object ctors = classIntrospector.get(clazz).get(ClassIntrospector.CONSTRUCTORS_KEY);
-            if (ctors == null) {
-                throw new TemplateModelException("Class " + clazz.getName() + 
-                        " has no public constructors.");
-            }
-            Constructor<?> ctor = null;
-            Object[] objargs;
-            if (ctors instanceof SimpleMethod) {
-                SimpleMethod sm = (SimpleMethod) ctors;
-                ctor = (Constructor<?>) sm.getMember();
-                objargs = sm.unwrapArguments(arguments, this);
-                try {
-                    return ctor.newInstance(objargs);
-                } catch (Exception e) {
-                    if (e instanceof TemplateModelException) throw (TemplateModelException) e;
-                    throw _MethodUtil.newInvocationTemplateModelException(null, ctor, e);
-                }
-            } else if (ctors instanceof OverloadedMethods) {
-                final MemberAndArguments mma = ((OverloadedMethods) ctors).getMemberAndArguments(arguments, this);
-                try {
-                    return mma.invokeConstructor(this);
-                } catch (Exception e) {
-                    if (e instanceof TemplateModelException) throw (TemplateModelException) e;
-                    
-                    throw _MethodUtil.newInvocationTemplateModelException(null, mma.getCallableMemberDescriptor(), e);
-                }
-            } else {
-                // Cannot happen
-                throw new BugException();
-            }
-        } catch (TemplateModelException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new TemplateModelException(
-                    "Error while creating new instance of class " + clazz.getName() + "; see cause exception", e);
-        }
-    }
-
-    /**
-     * Removes the introspection data for a class from the cache.
-     * Use this if you know that a class is not used anymore in templates.
-     * If the class will be still used, the cache entry will be silently
-     * re-created, so this isn't a dangerous operation.
-     * 
-     * @since 2.3.20
-     */
-    public void removeFromClassIntrospectionCache(Class<?> clazz) {
-        classIntrospector.remove(clazz);
-    }
-    
-    /**
-     * Removes all class introspection data from the cache.
-     * 
-     * <p>Use this if you want to free up memory on the expense of recreating
-     * the cache entries for the classes that will be used later in templates.
-     * 
-     * @throws IllegalStateException if {@link #isClassIntrospectionCacheRestricted()} is {@code true}.
-     * 
-     * @since 2.3.20
-     */
-    public void clearClassIntrospecitonCache() {
-        classIntrospector.clearCache();
-    }
-    
-    ClassIntrospector getClassIntrospector() {
-        return classIntrospector;
-    }
-    
-    /**
-     * Converts any {@link BigDecimal}s in the passed array to the type of
-     * the corresponding formal argument of the method.
-     */
-    // Unused?
-    public static void coerceBigDecimals(AccessibleObject callable, Object[] args) {
-        Class<?>[] formalTypes = null;
-        for (int i = 0; i < args.length; ++i) {
-            Object arg = args[i];
-            if (arg instanceof BigDecimal) {
-                if (formalTypes == null) {
-                    if (callable instanceof Method) {
-                        formalTypes = ((Method) callable).getParameterTypes();
-                    } else if (callable instanceof Constructor) {
-                        formalTypes = ((Constructor<?>) callable).getParameterTypes();
-                    } else {
-                        throw new IllegalArgumentException("Expected method or "
-                                + " constructor; callable is " + 
-                                callable.getClass().getName());
-                    }
-                }
-                args[i] = coerceBigDecimal((BigDecimal) arg, formalTypes[i]);
-            }
-        }
-    }
-    
-    /**
-     * Converts any {@link BigDecimal}-s in the passed array to the type of
-     * the corresponding formal argument of the method via {@link #coerceBigDecimal(BigDecimal, Class)}.
-     */
-    public static void coerceBigDecimals(Class<?>[] formalTypes, Object[] args) {
-        int typeLen = formalTypes.length;
-        int argsLen = args.length;
-        int min = Math.min(typeLen, argsLen);
-        for (int i = 0; i < min; ++i) {
-            Object arg = args[i];
-            if (arg instanceof BigDecimal) {
-                args[i] = coerceBigDecimal((BigDecimal) arg, formalTypes[i]);
-            }
-        }
-        if (argsLen > typeLen) {
-            Class<?> varArgType = formalTypes[typeLen - 1];
-            for (int i = typeLen; i < argsLen; ++i) {
-                Object arg = args[i];
-                if (arg instanceof BigDecimal) {
-                    args[i] = coerceBigDecimal((BigDecimal) arg, varArgType);
-                }
-            }
-        }
-    }
-
-    /**
-     * Converts {@link BigDecimal} to the class given in the {@code formalType} argument if that's a known numerical
-     * type, returns the {@link BigDecimal} as is otherwise. Overflow and precision loss are possible, similarly as
-     * with casting in Java.
-     */
-    public static Object coerceBigDecimal(BigDecimal bd, Class<?> formalType) {
-        // int is expected in most situations, so we check it first
-        if (formalType == int.class || formalType == Integer.class) {
-            return Integer.valueOf(bd.intValue());
-        } else if (formalType == double.class || formalType == Double.class) {
-            return Double.valueOf(bd.doubleValue());
-        } else if (formalType == long.class || formalType == Long.class) {
-            return Long.valueOf(bd.longValue());
-        } else if (formalType == float.class || formalType == Float.class) {
-            return Float.valueOf(bd.floatValue());
-        } else if (formalType == short.class || formalType == Short.class) {
-            return Short.valueOf(bd.shortValue());
-        } else if (formalType == byte.class || formalType == Byte.class) {
-            return Byte.valueOf(bd.byteValue());
-        } else if (java.math.BigInteger.class.isAssignableFrom(formalType)) {
-            return bd.toBigInteger();
-        } else {
-            return bd;
-        }
-    }
-    
-    /**
-     * Returns the exact class name and the identity hash, also the values of the most often used {@link BeansWrapper}
-     * configuration properties, also if which (if any) shared class introspection cache it uses.
-     *  
-     * @since 2.3.21
-     */
-    @Override
-    public String toString() {
-        final String propsStr = toPropertiesString();
-        return _ClassUtil.getShortClassNameOfObject(this) + "@" + System.identityHashCode(this)
-                + "(" + incompatibleImprovements + ", "
-                + (propsStr.length() != 0 ? propsStr + ", ..." : "")
-                + ")";
-    }
-    
-    /**
-     * Returns the name-value pairs that describe the configuration of this {@link BeansWrapper}; called from
-     * {@link #toString()}. The expected format is like {@code "foo=bar, baaz=wombat"}. When overriding this, you should
-     * call the super method, and then insert the content before it with a following {@code ", "}, or after it with a
-     * preceding {@code ", "}.
-     * 
-     * @since 2.3.22
-     */
-    protected String toPropertiesString() {
-        // Start with "simpleMapWrapper", because the override in DefaultObjectWrapper expects it to be there!
-        return "simpleMapWrapper=" + simpleMapWrapper + ", "
-               + "exposureLevel=" + classIntrospector.getExposureLevel() + ", "
-               + "exposeFields=" + classIntrospector.getExposeFields() + ", "
-               + "sharedClassIntrospCache="
-               + (classIntrospector.isShared() ? "@" + System.identityHashCode(classIntrospector) : "none");
-    }
-
-    private static ClassBasedModelFactory createEnumModels(BeansWrapper wrapper) {
-        if (ENUMS_MODEL_CTOR != null) {
-            try {
-                return (ClassBasedModelFactory) ENUMS_MODEL_CTOR.newInstance(
-                        wrapper);
-            } catch (Exception e) {
-                throw new UndeclaredThrowableException(e);
-            }
-        } else {
-            return null;
-        }
-    }
-    
-    private static Constructor enumsModelCtor() {
-        try {
-            // Check if Enums are available on this platform
-            Class.forName("java.lang.Enum");
-            // If they are, return the appropriate constructor for enum models
-            return Class.forName(
-                "org.apache.freemarker.core.model.impl.beans._EnumModels").getDeclaredConstructor(
-                    BeansWrapper.class);
-        } catch (Exception e) {
-            // Otherwise, return null
-            return null;
-        }
-    }
-
-    /**
-     * Used for
-     * {@link MethodAppearanceFineTuner#process}
-     * to store the results; see there.
-     */
-    static public final class MethodAppearanceDecision {
-        private PropertyDescriptor exposeAsProperty;
-        private String exposeMethodAs;
-        private boolean methodShadowsProperty;
-        
-        void setDefaults(Method m) {
-            exposeAsProperty = null;
-            exposeMethodAs = m.getName();
-            methodShadowsProperty = true;
-        }
-        
-        public PropertyDescriptor getExposeAsProperty() {
-            return exposeAsProperty;
-        }
-        
-        public void setExposeAsProperty(PropertyDescriptor exposeAsProperty) {
-            this.exposeAsProperty = exposeAsProperty;
-        }
-        
-        public String getExposeMethodAs() {
-            return exposeMethodAs;
-        }
-        
-        public void setExposeMethodAs(String exposeMethodAs) {
-            this.exposeMethodAs = exposeMethodAs;
-        }
-        
-        public boolean getMethodShadowsProperty() {
-            return methodShadowsProperty;
-        }
-        
-        public void setMethodShadowsProperty(boolean methodShadowsProperty) {
-            this.methodShadowsProperty = methodShadowsProperty;
-        }
-
-    }
-    
-    /**
-     * Used for {@link MethodAppearanceFineTuner#process} as input parameter; see there.
-     */
-    static public final class MethodAppearanceDecisionInput {
-        private Method method;
-        private Class<?> containingClass;
-        
-        void setMethod(Method method) {
-            this.method = method;
-        }
-        
-        void setContainingClass(Class<?> containingClass) {
-            this.containingClass = containingClass;
-        }
-
-        public Method getMethod() {
-            return method;
-        }
-
-        public Class/*<?>*/ getContainingClass() {
-            return containingClass;
-        }
-        
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBuilder.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBuilder.java
deleted file mode 100644
index 2c0dcc0..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperBuilder.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.beans;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
-
-/**
- * Gets/creates a {@link BeansWrapper} singleton instance that's already configured as specified in the properties of
- * this object; this is recommended over using the {@link BeansWrapper} 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>
- *    BeansWrapper beansWrapper = new BeansWrapperBuilder(Configuration.VERSION_2_3_21).build();
- * </pre>
- * 
- * <p>Or, a more complex example:</p>
- * <pre>
- *    // Create the builder:
- *    BeansWrapperBuilder builder = new BeansWrapperBuilder(Configuration.VERSION_2_3_21);
- *    // Set desired BeansWrapper configuration properties:
- *    builder.setUseModelCache(true);
- *    builder.setExposeFields(true);
- *    
- *    // Get the singleton:
- *    BeansWrapper beansWrapper = 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 BeansWrapper} 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.jar}-class-loader-level) cache. Also the {@link BeansWrapper} 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 BeansWrapper}
- * 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 BeansWrapper} 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 BeansWrapper}
- *     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 BeansWrapperBuilder#setExposeFields(boolean) expose_fields} and
- *     {@link BeansWrapperBuilder#setExposureLevel(int) exposure_level}. So only {@link BeansWrapper} 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 BeansWrapper}. {@link BeansWrapperBuilder} returns the same
- *     {@link BeansWrapper} 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 BeansWrapper} 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 BeansWrapper#getEnumModels()} and
- *         {@link BeansWrapper#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 BeansWrapper#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 BeansWrapper} also applies to
- * {@link DefaultObjectWrapperBuilder}.
- * 
- * @since 2.3.21
- */
-public class BeansWrapperBuilder extends BeansWrapperConfiguration {
-
-    private final static Map<ClassLoader, Map<BeansWrapperConfiguration, WeakReference<BeansWrapper>>>
-            INSTANCE_CACHE = new WeakHashMap<>();
-    private final static ReferenceQueue<BeansWrapper> INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue<>();
-   
-    private static class BeansWrapperFactory
-            implements _BeansAPI._BeansWrapperSubclassFactory<BeansWrapper, BeansWrapperConfiguration> {
-        
-        private static final BeansWrapperFactory INSTANCE = new BeansWrapperFactory(); 
-
-        @Override
-        public BeansWrapper create(BeansWrapperConfiguration bwConf) {
-            return new BeansWrapper(bwConf, true);
-        }
-        
-    }
-
-    /**
-     * See {@link BeansWrapperConfiguration#BeansWrapperConfiguration(Version)}.
-     */
-    public BeansWrapperBuilder(Version incompatibleImprovements) {
-        super(incompatibleImprovements);
-    }
-    
-    /** For unit testing only */
-    static void clearInstanceCache() {
-        synchronized (INSTANCE_CACHE) {
-            INSTANCE_CACHE.clear();
-        }
-    }
-
-    /**
-     * For unit testing only 
-     */
-    static Map<ClassLoader, Map<BeansWrapperConfiguration, WeakReference<BeansWrapper>>> getInstanceCache() {
-        return INSTANCE_CACHE;
-    }
-
-    /**
-     * Returns a {@link BeansWrapper} instance that matches the settings of this builder. This will be possibly a
-     * singleton that is also in use elsewhere, not necessarily a new object.
-     */
-    public BeansWrapper build() {
-        return _BeansAPI.getBeansWrapperSubclassSingleton(
-                this, INSTANCE_CACHE, INSTANCE_CACHE_REF_QUEUE, BeansWrapperFactory.INSTANCE);
-    }
-    
-}
\ No newline at end of file


[10/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethodsSubset.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethodsSubset.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethodsSubset.java
deleted file mode 100644
index 8f41200..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethodsSubset.java
+++ /dev/null
@@ -1,402 +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.beans;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.util._ClassUtil;
-import org.apache.freemarker.core.util._NullArgumentException;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Encapsulates the rules and data structures (including cache) for choosing of the best matching callable member for
- * a parameter list, from a given set of callable members. There are two subclasses of this, one for non-varags methods,
- * and one for varargs methods.
- */
-abstract class OverloadedMethodsSubset {
-    
-    /** 
-     * Used for an optimization trick to substitute an array of whatever size that contains only 0-s. Since this array
-     * is 0 long, this means that the code that reads the int[] always have to check if the int[] has this value, and
-     * then it has to act like if was all 0-s.  
-     */
-    static final int[] ALL_ZEROS_ARRAY = new int[0];
-
-    private static final int[][] ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY = new int[1][];
-    static {
-        ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY[0] = ALL_ZEROS_ARRAY;
-    }
-
-    private Class[/*number of args*/][/*arg index*/] unwrappingHintsByParamCount;
-    
-    /**
-     * Tells what types occur at a given parameter position with a bit field. See {@link TypeFlags}.
-     */
-    private int[/*number of args*/][/*arg index*/] typeFlagsByParamCount;
-    
-    // TODO: This can cause memory-leak when classes are re-loaded. However, first the genericClassIntrospectionCache
-    // and such need to be oms in this regard. 
-    private final Map/*<ArgumentTypes, MaybeEmptyCallableMemberDescriptor>*/ argTypesToMemberDescCache
-            = new ConcurrentHashMap(6, 0.75f, 1);
-    
-    private final List/*<ReflectionCallableMemberDescriptor>*/ memberDescs = new LinkedList();
-    
-    OverloadedMethodsSubset() {
-        //
-    }
-    
-    void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) {
-        memberDescs.add(memberDesc);
-        
-        // Warning: Do not modify this array, or put it into unwrappingHintsByParamCount by reference, as the arrays
-        // inside that are modified!
-        final Class[] prepedParamTypes = preprocessParameterTypes(memberDesc);
-        final int paramCount = prepedParamTypes.length;  // Must be the same as the length of the original param list
-        
-        // Merge these unwrapping hints with the existing table of hints:
-        if (unwrappingHintsByParamCount == null) {
-            unwrappingHintsByParamCount = new Class[paramCount + 1][];
-            unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
-        } else if (unwrappingHintsByParamCount.length <= paramCount) {
-            Class[][] newUnwrappingHintsByParamCount = new Class[paramCount + 1][];
-            System.arraycopy(unwrappingHintsByParamCount, 0, newUnwrappingHintsByParamCount, 0,
-                    unwrappingHintsByParamCount.length);
-            unwrappingHintsByParamCount = newUnwrappingHintsByParamCount;
-            unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
-        } else {
-            Class[] unwrappingHints = unwrappingHintsByParamCount[paramCount]; 
-            if (unwrappingHints == null) {
-                unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
-            } else {
-                for (int paramIdx = 0; paramIdx < prepedParamTypes.length; paramIdx++) {
-                    // For each parameter list length, we merge the argument type arrays into a single Class[] that
-                    // stores the most specific common types for each position. Hence we will possibly use a too generic
-                    // hint for the unwrapping. For correct behavior, for each overloaded methods its own parameter
-                    // types should be used as a hint. But without unwrapping the arguments, we couldn't select the
-                    // overloaded method. So we had to unwrap with all possible target types of each parameter position,
-                    // which would be slow and its result would be uncacheable (as we don't have anything usable as
-                    // a lookup key). So we just use this compromise.
-                    unwrappingHints[paramIdx] = getCommonSupertypeForUnwrappingHint(
-                            unwrappingHints[paramIdx], prepedParamTypes[paramIdx]);
-                }
-            }
-        }
-
-        int[] typeFlagsByParamIdx = ALL_ZEROS_ARRAY;
-        // Fill typeFlagsByParamCount (if necessary)
-        for (int paramIdx = 0; paramIdx < paramCount; paramIdx++) {
-            final int typeFlags = TypeFlags.classToTypeFlags(prepedParamTypes[paramIdx]);
-            if (typeFlags != 0) {
-                if (typeFlagsByParamIdx == ALL_ZEROS_ARRAY) {
-                    typeFlagsByParamIdx = new int[paramCount];
-                }
-                typeFlagsByParamIdx[paramIdx] = typeFlags;
-            }
-        }
-        mergeInTypesFlags(paramCount, typeFlagsByParamIdx);
-        
-        afterWideningUnwrappingHints(prepedParamTypes, typeFlagsByParamIdx);
-    }
-    
-    Class[][] getUnwrappingHintsByParamCount() {
-        return unwrappingHintsByParamCount;
-    }
-    
-    @SuppressFBWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
-            justification="Locks for member descriptor creation only")
-    final MaybeEmptyCallableMemberDescriptor getMemberDescriptorForArgs(Object[] args, boolean varArg) {
-        ArgumentTypes argTypes = new ArgumentTypes(args);
-        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 (argTypesToMemberDescCache) {
-                memberDesc = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes);
-                if (memberDesc == null) {
-                    memberDesc = argTypes.getMostSpecific(memberDescs, varArg);
-                    argTypesToMemberDescCache.put(argTypes, memberDesc);
-                }
-            }
-        }
-        return memberDesc;
-    }
-    
-    Iterator/*<ReflectionCallableMemberDescriptor>*/ getMemberDescriptors() {
-        return memberDescs.iterator();
-    }
-    
-    abstract Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc);
-    abstract void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes);
-    
-    abstract MaybeEmptyMemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs, 
-            BeansWrapper unwrapper) throws TemplateModelException;
-
-    /**
-     * Returns the most specific common class (or interface) of two parameter types for the purpose of unwrapping.
-     * This is trickier than finding the most specific overlapping superclass of two classes, because:
-     * <ul>
-     *   <li>It considers primitive classes as the subclasses of the boxing classes.</li>
-     *   <li>If the only common class is {@link Object}, it will try to find a common interface. If there are more
-     *       of them, it will start removing those that are known to be uninteresting as unwrapping hints.</li>
-     * </ul>
-     * 
-     * @param c1 Parameter type 1
-     * @param c2 Parameter type 2
-     */
-    protected Class getCommonSupertypeForUnwrappingHint(Class c1, Class c2) {
-        if (c1 == c2) return c1;
-        // This also means that the hint for (Integer, Integer) will be Integer, not just Number. This is consistent
-        // with how non-overloaded method hints work.
-        
-        // c1 primitive class to boxing class:
-        final boolean c1WasPrim; 
-        if (c1.isPrimitive()) {
-            c1 = _ClassUtil.primitiveClassToBoxingClass(c1);
-            c1WasPrim = true;
-        } else {
-            c1WasPrim = false;
-        }
-        
-        // c2 primitive class to boxing class:
-        final boolean c2WasPrim; 
-        if (c2.isPrimitive()) {
-            c2 = _ClassUtil.primitiveClassToBoxingClass(c2);
-            c2WasPrim = true;
-        } else {
-            c2WasPrim = false;
-        }
-
-        if (c1 == c2) {
-            // If it was like int and Integer, boolean and Boolean, etc., we return the boxing type (as that's the
-            // less specific, because it allows null.)
-            // (If it was two equivalent primitives, we don't get here, because of the 1st line of the method.) 
-            return c1;
-        } else if (Number.class.isAssignableFrom(c1) && Number.class.isAssignableFrom(c2)) {
-            // We don't want the unwrapper to convert to a numerical super-type [*] as it's not yet known what the
-            // actual number type of the chosen method will be. We will postpone the actual numerical conversion
-            // until that, especially as some conversions (like oms point to floating point) can be lossy.
-            // * Numerical super-type: Like long > int > short > byte.  
-            return Number.class;
-        } else if (c1WasPrim || c2WasPrim) {
-            // At this point these all stand:
-            // - At least one of them was primitive
-            // - No more than one of them was numerical
-            // - They don't have the same wrapper (boxing) class
-            return Object.class;
-        }
-        
-        // We never get to this point if buxfixed is true and any of these stands:
-        // - One of classes was a primitive type
-        // - One of classes was a numerical type (either boxing type or primitive)
-        
-        Set commonTypes = _MethodUtil.getAssignables(c1, c2);
-        commonTypes.retainAll(_MethodUtil.getAssignables(c2, c1));
-        if (commonTypes.isEmpty()) {
-            // Can happen when at least one of the arguments is an interface, as
-            // they don't have Object at the root of their hierarchy
-            return Object.class;
-        }
-        
-        // Gather maximally specific elements. Yes, there can be more than one 
-        // because of interfaces. I.e., if you call this method for String.class 
-        // and Number.class, you'll have Comparable, Serializable, and Object as 
-        // maximal elements. 
-        List max = new ArrayList();
-        listCommonTypes:  for (Iterator commonTypesIter = commonTypes.iterator(); commonTypesIter.hasNext(); ) {
-            Class clazz = (Class) commonTypesIter.next();
-            for (Iterator maxIter = max.iterator(); maxIter.hasNext(); ) {
-                Class maxClazz = (Class) maxIter.next();
-                if (_MethodUtil.isMoreOrSameSpecificParameterType(maxClazz, clazz, false /*bugfixed [1]*/, 0) != 0) {
-                    // clazz can't be maximal, if there's already a more specific or equal maximal than it.
-                    continue listCommonTypes;
-                }
-                if (_MethodUtil.isMoreOrSameSpecificParameterType(clazz, maxClazz, false /*bugfixed [1]*/, 0) != 0) {
-                    // If it's more specific than a currently maximal element,
-                    // that currently maximal is no longer a maximal.
-                    maxIter.remove();
-                }
-                // 1: We don't use bugfixed at the "[1]"-marked points because it's slower and doesn't make any
-                //    difference here as it's ensured that nor c1 nor c2 is primitive or numerical. The bugfix has only
-                //    affected the treatment of primitives and numerical types. 
-            }
-            // If we get here, no current maximal is more specific than the
-            // current class, so clazz is a new maximal so far.
-            max.add(clazz);
-        }
-        
-        if (max.size() > 1) {  // we have an ambiguity
-            // Find the non-interface class
-            for (Iterator it = max.iterator(); it.hasNext(); ) {
-                Class maxCl = (Class) it.next();
-                if (!maxCl.isInterface()) {
-                    if (maxCl != Object.class) {  // This actually shouldn't ever happen, but to be sure...
-                        // If it's not Object, we use it as the most specific
-                        return maxCl;
-                    } else {
-                        // Otherwise remove Object, and we will try with the interfaces 
-                        it.remove();
-                    }
-                }
-            }
-            
-            // At this point we only have interfaces left.
-            // Try removing interfaces about which we know that they are useless as unwrapping hints:
-            max.remove(Cloneable.class);
-            if (max.size() > 1) {  // Still have an ambiguity...
-                max.remove(Serializable.class);
-                if (max.size() > 1) {  // Still had an ambiguity...
-                    max.remove(Comparable.class);
-                    if (max.size() > 1) {
-                        return Object.class; // Still had an ambiguity... no luck.
-                    }
-                }
-            }
-        }
-        
-        return (Class) max.get(0);
-    }
-    
-    /**
-     * Gets the "type flags" of each parameter positions, or {@code null} if there's no method with this parameter
-     * count or if we are in pre-2.3.21 mode, or {@link #ALL_ZEROS_ARRAY} if there were no parameters that turned
-     * on a flag. The returned {@code int}-s are one or more {@link TypeFlags} constants binary "or"-ed together.  
-     */
-    final protected int[] getTypeFlags(int paramCount) {
-        return typeFlagsByParamCount != null && typeFlagsByParamCount.length > paramCount
-                ? typeFlagsByParamCount[paramCount]
-                : null;
-    }
-
-    /**
-     * Updates the content of the {@link #typeFlagsByParamCount} field with the parameter type flags of a method.
-     * 
-     * @param dstParamCount The parameter count for which we want to merge in the type flags 
-     * @param srcTypeFlagsByParamIdx If shorter than {@code dstParamCount}, its last item will be repeated until
-     *        dstParamCount length is reached. If longer, the excessive items will be ignored.
-     *        Maybe {@link #ALL_ZEROS_ARRAY}. Maybe a 0-length array. Can't be {@code null}.
-     */
-    final protected void mergeInTypesFlags(int dstParamCount, int[] srcTypeFlagsByParamIdx) {
-        _NullArgumentException.check("srcTypesFlagsByParamIdx", srcTypeFlagsByParamIdx);
-        
-        // Special case of 0 param count:
-        if (dstParamCount == 0) {
-            if (typeFlagsByParamCount == null) {
-                typeFlagsByParamCount = ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY;
-            } else if (typeFlagsByParamCount != ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY) {
-                typeFlagsByParamCount[0] = ALL_ZEROS_ARRAY;
-            }
-            return;
-        }
-        
-        // Ensure that typesFlagsByParamCount[dstParamCount] exists:
-        if (typeFlagsByParamCount == null) {
-            typeFlagsByParamCount = new int[dstParamCount + 1][];
-        } else if (typeFlagsByParamCount.length <= dstParamCount) {
-            int[][] newTypeFlagsByParamCount = new int[dstParamCount + 1][];
-            System.arraycopy(typeFlagsByParamCount, 0, newTypeFlagsByParamCount, 0,
-                    typeFlagsByParamCount.length);
-            typeFlagsByParamCount = newTypeFlagsByParamCount;
-        }
-        
-        int[] dstTypeFlagsByParamIdx = typeFlagsByParamCount[dstParamCount];
-        if (dstTypeFlagsByParamIdx == null) {
-            // This is the first method added with this number of params => no merging
-            
-            if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) {
-                int srcParamCount = srcTypeFlagsByParamIdx.length;
-                dstTypeFlagsByParamIdx = new int[dstParamCount];
-                for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
-                    dstTypeFlagsByParamIdx[paramIdx]
-                            = srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1];
-                }
-            } else {
-                dstTypeFlagsByParamIdx = ALL_ZEROS_ARRAY;
-            }
-            
-            typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx;
-        } else {
-            // dstTypeFlagsByParamIdx != null, so we need to merge into it.
-            
-            if (srcTypeFlagsByParamIdx == dstTypeFlagsByParamIdx) {
-                // Used to occur when both are ALL_ZEROS_ARRAY
-                return;
-            }
-            
-            // As we will write dstTypeFlagsByParamIdx, it can't remain ALL_ZEROS_ARRAY anymore. 
-            if (dstTypeFlagsByParamIdx == ALL_ZEROS_ARRAY && dstParamCount > 0) {
-                dstTypeFlagsByParamIdx = new int[dstParamCount];
-                typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx;
-            }
-            
-            for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
-                final int srcParamTypeFlags;
-                if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) {
-                    int srcParamCount = srcTypeFlagsByParamIdx.length;
-                    srcParamTypeFlags = srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1]; 
-                } else {
-                    srcParamTypeFlags = 0;
-                }
-                
-                final int dstParamTypesFlags = dstTypeFlagsByParamIdx[paramIdx];
-                if (dstParamTypesFlags != srcParamTypeFlags) {
-                    int mergedTypeFlags = dstParamTypesFlags | srcParamTypeFlags;
-                    if ((mergedTypeFlags & TypeFlags.MASK_ALL_NUMERICALS) != 0) {
-                        // Must not be set if we don't have numerical type at this index! 
-                        mergedTypeFlags |= TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT;
-                    }
-                    dstTypeFlagsByParamIdx[paramIdx] = mergedTypeFlags; 
-                }
-            }
-        }
-    }
-    
-    protected void forceNumberArgumentsToParameterTypes(
-            Object[] args, Class[] paramTypes, int[] typeFlagsByParamIndex) {
-        final int paramTypesLen = paramTypes.length;
-        final int argsLen = args.length;
-        for (int argIdx = 0; argIdx < argsLen; argIdx++) {
-            final int paramTypeIdx = argIdx < paramTypesLen ? argIdx : paramTypesLen - 1;
-            final int typeFlags = typeFlagsByParamIndex[paramTypeIdx];
-            
-            // Forcing the number type can only be interesting if there are numerical parameter types on that index,
-            // and the unwrapping was not to an exact numerical type.
-            if ((typeFlags & TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT) != 0) {
-                final Object arg = args[argIdx];
-                // If arg isn't a number, we can't do any conversions anyway, regardless of the param type.
-                if (arg instanceof Number) {
-                    final Class targetType = paramTypes[paramTypeIdx];
-                    final Number convertedArg = BeansWrapper.forceUnwrappedNumberToType((Number) arg, targetType);
-                    if (convertedArg != null) {
-                        args[argIdx] = convertedArg;
-                    }
-                }
-            }
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedNumberUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedNumberUtil.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedNumberUtil.java
deleted file mode 100644
index 4d4ae97..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedNumberUtil.java
+++ /dev/null
@@ -1,1289 +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.beans;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.util._ClassUtil;
-import org.apache.freemarker.core.util._NumberUtil;
-
-/**
- * Everything related to coercion to ambiguous numerical types.  
- */
-class OverloadedNumberUtil {
-
-    // Can't be instantiated
-    private OverloadedNumberUtil() { }
-
-    /**
-     * The lower limit of conversion prices where there's a risk of significant mantissa loss.
-     * The value comes from misc/overloadedNumberRules/prices.ods and generator.ftl.
-     */
-    static final int BIG_MANTISSA_LOSS_PRICE = 4 * 10000;
-    
-    /** The highest long that can be stored in double without precision loss: 2**53. */
-    private static final long MAX_DOUBLE_OR_LONG = 9007199254740992L;
-    /** The lowest long that can be stored in double without precision loss: -(2**53). */
-    private static final long MIN_DOUBLE_OR_LONG = -9007199254740992L;
-    private static final int MAX_DOUBLE_OR_LONG_LOG_2 = 53;
-    
-    /** The highest long that can be stored in float without precision loss: 2**24. */
-    private static final int MAX_FLOAT_OR_INT = 16777216;
-    /** The lowest long that can be stored in float without precision loss: -(2**24). */
-    private static final int MIN_FLOAT_OR_INT = -16777216;
-    private static final int MAX_FLOAT_OR_INT_LOG_2 = 24;
-    /** Lowest number that we don't thread as possible integer 0. */
-    private static final double LOWEST_ABOVE_ZERO = 0.000001;
-    /** Highest number that we don't thread as possible integer 1. */
-    private static final double HIGHEST_BELOW_ONE = 0.999999;
-
-    /**
-     * Attaches the lowest alternative number type to the parameter number via {@link NumberWithFallbackType}, if
-     * that's useful according the possible target number types. This transformation is applied on the method call
-     * argument list before overloaded method selection.
-     * 
-     * <p>Note that as of this writing, this method is only used when
-     * {@link BeansWrapper#getIncompatibleImprovements()} >= 2.3.21.
-     * 
-     * <p>Why's this needed, how it works: Overloaded method selection only selects methods where the <em>type</em>
-     * (not the value!) of the argument is "smaller" or the same as the parameter type. This is similar to how it's in
-     * the Java language. That it only decides based on the parameter type is important because this way
-     * {@link OverloadedMethodsSubset} can cache method lookup decisions using the types as the cache key. Problem is,
-     * since you don't declare the exact numerical types in FTL, and FTL has only a single generic numeric type
-     * anyway, what Java type a {@link TemplateNumberModel} uses internally is often seen as a technical detail of which
-     * the template author can't always keep track of. So we investigate the <em>value</em> of the number too,
-     * then coerce it down without overflow to a type that will match the most overloaded methods. (This
-     * is especially important as FTL often stores numbers in {@link BigDecimal}-s, which will hardly ever match any
-     * method parameters.) We could simply return that number, like {@code Byte(0)} for an {@code Integer(0)},
-     * however, then we would lose the information about what the original type was. The original type is sometimes
-     * important, as in ambiguous situations the method where there's an exact type match should be selected (like,
-     * when someone wants to select an overload explicitly with {@code m(x?int)}). Also, if an overload wins where
-     * the parameter type at the position of the number is {@code Number} or {@code Object} (or {@code Comparable}
-     * etc.), it's expected that we pass in the original value (an {@code Integer} in this example), especially if that
-     * value is the return value of another Java method. That's why we use
-     * {@link NumberWithFallbackType} numerical classes like {@link IntegerOrByte}, which represents both the original
-     * type and the coerced type, all encoded into the class of the value, which is used as the overloaded method lookup
-     * cache key.
-     *  
-     * <p>See also: <tt>src\main\misc\overloadedNumberRules\prices.ods</tt>.
-     * 
-     * @param num the number to coerce
-     * @param typeFlags the type flags of the target parameter position; see {@link TypeFlags}
-     * 
-     * @returns The original number or a {@link NumberWithFallbackType}, depending on the actual value and the types
-     *     indicated in the {@code targetNumTypes} parameter.
-     */
-    static Number addFallbackType(final Number num, final int typeFlags) {
-        final Class numClass = num.getClass();
-        if (numClass == BigDecimal.class) {
-            // For now we only support the backward-compatible mode that doesn't prevent roll overs and magnitude loss.
-            // However, we push the overloaded selection to the right direction, so we will at least indicate if the
-            // number has decimals.
-            BigDecimal n = (BigDecimal) num; 
-            if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) != 0
-                    && (typeFlags & TypeFlags.MASK_KNOWN_NONINTEGERS) != 0
-                    && _NumberUtil.isIntegerBigDecimal(n) /* <- can be expensive */) {
-                return new IntegerBigDecimal(n);
-            } else {
-                // Either it was a non-integer, or it didn't mater what it was, as we don't have both integer and
-                // non-integer target types. 
-                return n;
-            }
-        } else if (numClass == Integer.class) {
-            int pn = num.intValue();
-            // Note that we try to return the most specific type (i.e., the numerical type with the smallest range), but
-            // only among the types that are possible targets. Like if the only target is int and the value is 1, we
-            // will return Integer 1, not Byte 1, even though byte is automatically converted to int so it would
-            // work too. Why we avoid unnecessarily specific types is that they generate more overloaded method lookup
-            // cache entries, since the cache key is the array of the types of the argument values. So we want as few
-            // permutations as possible. 
-            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
-                return new IntegerOrByte((Integer) num, (byte) pn);
-            } else if ((typeFlags & TypeFlags.SHORT) != 0 && pn <= Short.MAX_VALUE && pn >= Short.MIN_VALUE) {
-                return new IntegerOrShort((Integer) num, (short) pn);
-            } else {
-                return num;
-            }
-        } else if (numClass == Long.class) {
-            final long pn = num.longValue(); 
-            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
-                return new LongOrByte((Long) num, (byte) pn);
-            } else if ((typeFlags & TypeFlags.SHORT) != 0 && pn <= Short.MAX_VALUE && pn >= Short.MIN_VALUE) {
-                return new LongOrShort((Long) num, (short) pn);
-            } else if ((typeFlags & TypeFlags.INTEGER) != 0 && pn <= Integer.MAX_VALUE && pn >= Integer.MIN_VALUE) {
-                return new LongOrInteger((Long) num, (int) pn);
-            } else {
-                return num;
-            }
-        } else if (numClass == Double.class) {
-            final double doubleN = num.doubleValue();
-            
-            // Can we store it in an integer type?
-            checkIfWholeNumber: do {
-                if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) == 0) break checkIfWholeNumber;
-                
-                // There's no hope to be 1-precise outside this region. (Although problems can occur even inside it...)
-                if (doubleN > MAX_DOUBLE_OR_LONG || doubleN < MIN_DOUBLE_OR_LONG) break checkIfWholeNumber;
-                
-                long longN = num.longValue(); 
-                double diff = doubleN - longN;
-                boolean exact;  // We will try to ignore precision glitches (like 0.3 - 0.2 - 0.1 = -2.7E-17)
-                if (diff == 0) {
-                    exact = true;
-                } else if (diff > 0) {
-                    if (diff < LOWEST_ABOVE_ZERO) {
-                        exact = false;
-                    } else if (diff > HIGHEST_BELOW_ONE) {
-                        exact = false;
-                        longN++;
-                    } else {
-                        break checkIfWholeNumber;
-                    }
-                } else {  // => diff < 0
-                    if (diff > -LOWEST_ABOVE_ZERO) {
-                        exact = false;
-                    } else if (diff < -HIGHEST_BELOW_ONE) {
-                        exact = false;
-                        longN--;
-                    } else {
-                        break checkIfWholeNumber;
-                    }
-                }
-                
-                // If we reach this, it can be treated as a whole number.
-                
-                if ((typeFlags & TypeFlags.BYTE) != 0
-                        && longN <= Byte.MAX_VALUE && longN >= Byte.MIN_VALUE) {
-                    return new DoubleOrByte((Double) num, (byte) longN);
-                } else if ((typeFlags & TypeFlags.SHORT) != 0
-                        && longN <= Short.MAX_VALUE && longN >= Short.MIN_VALUE) {
-                    return new DoubleOrShort((Double) num, (short) longN);
-                } else if ((typeFlags & TypeFlags.INTEGER) != 0
-                        && longN <= Integer.MAX_VALUE && longN >= Integer.MIN_VALUE) {
-                    final int intN = (int) longN; 
-                    return (typeFlags & TypeFlags.FLOAT) != 0 && intN >= MIN_FLOAT_OR_INT && intN <= MAX_FLOAT_OR_INT
-                                    ? new DoubleOrIntegerOrFloat((Double) num, intN)
-                                    : new DoubleOrInteger((Double) num, intN);
-                } else if ((typeFlags & TypeFlags.LONG) != 0) {
-                    if (exact) {
-                        return new DoubleOrLong((Double) num, longN);
-                    } else {
-                        // We don't deal with non-exact numbers outside the range of int, as we already reach
-                        // ULP 2.384185791015625E-7 there.
-                        if (longN >= Integer.MIN_VALUE && longN <= Integer.MAX_VALUE) {
-                            return new DoubleOrLong((Double) num, longN);
-                        } else {
-                            break checkIfWholeNumber;
-                        }
-                    }
-                }
-                // This point is reached if the double value was out of the range of target integer type(s). 
-                // Falls through!
-            } while (false);
-            // If we reach this that means that it can't be treated as a whole number.
-            
-            if ((typeFlags & TypeFlags.FLOAT) != 0 && doubleN >= -Float.MAX_VALUE && doubleN <= Float.MAX_VALUE) {
-                return new DoubleOrFloat((Double) num);
-            } else {
-                // Simply Double:
-                return num;
-            }
-        } else if (numClass == Float.class) {
-            final float floatN = num.floatValue();
-            
-            // Can we store it in an integer type?
-            checkIfWholeNumber: do {
-                if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) == 0) break checkIfWholeNumber;
-                
-                // There's no hope to be 1-precise outside this region. (Although problems can occur even inside it...)
-                if (floatN > MAX_FLOAT_OR_INT || floatN < MIN_FLOAT_OR_INT) break checkIfWholeNumber;
-                
-                int intN = num.intValue();
-                double diff = floatN - intN;
-                boolean exact;  // We will try to ignore precision glitches (like 0.3 - 0.2 - 0.1 = -2.7E-17)
-                if (diff == 0) {
-                    exact = true;
-                // We already reach ULP 7.6293945E-6 with bytes, so we don't continue with shorts.
-                } else if (intN >= Byte.MIN_VALUE && intN <= Byte.MAX_VALUE) {
-                    if (diff > 0) {
-                        if (diff < 0.00001) {
-                            exact = false;
-                        } else if (diff > 0.99999) {
-                            exact = false;
-                            intN++;
-                        } else {
-                            break checkIfWholeNumber;
-                        }
-                    } else {  // => diff < 0
-                        if (diff > -0.00001) {
-                            exact = false;
-                        } else if (diff < -0.99999) {
-                            exact = false;
-                            intN--;
-                        } else {
-                            break checkIfWholeNumber;
-                        }
-                    }
-                } else {
-                    break checkIfWholeNumber;
-                }
-                
-                // If we reach this, it can be treated as a whole number.
-                
-                if ((typeFlags & TypeFlags.BYTE) != 0 && intN <= Byte.MAX_VALUE && intN >= Byte.MIN_VALUE) {
-                    return new FloatOrByte((Float) num, (byte) intN);
-                } else if ((typeFlags & TypeFlags.SHORT) != 0 && intN <= Short.MAX_VALUE && intN >= Short.MIN_VALUE) {
-                    return new FloatOrShort((Float) num, (short) intN);
-                } else if ((typeFlags & TypeFlags.INTEGER) != 0) {
-                    return new FloatOrInteger((Float) num, intN);
-                } else if ((typeFlags & TypeFlags.LONG) != 0) {
-                    // We can't even go outside the range of integers, so we don't need Long variation:
-                    return exact
-                            ? new FloatOrInteger((Float) num, intN)
-                            : new FloatOrByte((Float) num, (byte) intN);  // as !exact implies (-128..127)
-                }
-                // This point is reached if the float value was out of the range of target integer type(s). 
-                // Falls through!
-            } while (false);
-            // If we reach this that means that it can't be treated as a whole number. So it's simply a Float:
-            return num;
-        } else if (numClass == Byte.class) {
-            return num;
-        } else if (numClass == Short.class) {
-            short pn = num.shortValue(); 
-            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
-                return new ShortOrByte((Short) num, (byte) pn);
-            } else {
-                return num;
-            }
-        } else if (numClass == BigInteger.class) {
-            if ((typeFlags
-                    & ((TypeFlags.MASK_KNOWN_INTEGERS | TypeFlags.MASK_KNOWN_NONINTEGERS)
-                            ^ (TypeFlags.BIG_INTEGER | TypeFlags.BIG_DECIMAL))) != 0) {
-                BigInteger biNum = (BigInteger) num;
-                final int bitLength = biNum.bitLength();  // Doesn't include sign bit, so it's one less than expected
-                if ((typeFlags & TypeFlags.BYTE) != 0 && bitLength <= 7) {
-                    return new BigIntegerOrByte(biNum);
-                } else if ((typeFlags & TypeFlags.SHORT) != 0 && bitLength <= 15) {
-                    return new BigIntegerOrShort(biNum);
-                } else if ((typeFlags & TypeFlags.INTEGER) != 0 && bitLength <= 31) {
-                    return new BigIntegerOrInteger(biNum);
-                } else if ((typeFlags & TypeFlags.LONG) != 0 && bitLength <= 63) {
-                    return new BigIntegerOrLong(biNum);
-                } else if ((typeFlags & TypeFlags.FLOAT) != 0
-                        && (bitLength <= MAX_FLOAT_OR_INT_LOG_2
-                            || bitLength == MAX_FLOAT_OR_INT_LOG_2 + 1
-                               && biNum.getLowestSetBit() >= MAX_FLOAT_OR_INT_LOG_2)) {
-                    return new BigIntegerOrFloat(biNum);
-                } else if ((typeFlags & TypeFlags.DOUBLE) != 0
-                        && (bitLength <= MAX_DOUBLE_OR_LONG_LOG_2
-                            || bitLength == MAX_DOUBLE_OR_LONG_LOG_2 + 1
-                               && biNum.getLowestSetBit() >= MAX_DOUBLE_OR_LONG_LOG_2)) {
-                    return new BigIntegerOrDouble(biNum);
-                } else {
-                    return num;
-                }
-            } else {
-                // No relevant coercion target types; return the BigInteger as is:
-                return num;
-            }
-        } else {
-            // Unknown number type:
-            return num;
-        }
-    }
-
-    interface ByteSource { Byte byteValue(); }
-    interface ShortSource { Short shortValue(); }
-    interface IntegerSource { Integer integerValue(); }
-    interface LongSource { Long longValue(); }
-    interface FloatSource { Float floatValue(); }
-    interface DoubleSource { Double doubleValue(); }
-    interface BigIntegerSource { BigInteger bigIntegerValue(); }
-    interface BigDecimalSource { BigDecimal bigDecimalValue(); }
-    
-    /**
-     * Superclass of "Or"-ed numerical types. With an example, a {@code int} 1 has the fallback type {@code byte}, as
-     * that's the smallest type that can store the value, so it can be represented as an {@link IntegerOrByte}.
-     * This is useful as overloaded method selection only examines the type of the arguments, not the value of them,
-     * but with "Or"-ed types we can encode this value-related information into the argument type, hence influencing the
-     * method selection.
-     */
-    abstract static class NumberWithFallbackType extends Number implements Comparable {
-        
-        protected abstract Number getSourceNumber();
-
-        @Override
-        public int intValue() {
-            return getSourceNumber().intValue();
-        }
-
-        @Override
-        public long longValue() {
-            return getSourceNumber().longValue();
-        }
-
-        @Override
-        public float floatValue() {
-            return getSourceNumber().floatValue();
-        }
-
-        @Override
-        public double doubleValue() {
-            return getSourceNumber().doubleValue();
-        }
-
-        @Override
-        public byte byteValue() {
-            return getSourceNumber().byteValue();
-        }
-
-        @Override
-        public short shortValue() {
-            return getSourceNumber().shortValue();
-        }
-
-        @Override
-        public int hashCode() {
-            return getSourceNumber().hashCode();
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj != null && getClass() == obj.getClass()) {
-                return getSourceNumber().equals(((NumberWithFallbackType) obj).getSourceNumber());
-            } else {
-                return false;
-            }
-        }
-
-        @Override
-        public String toString() {
-            return getSourceNumber().toString();
-        }
-
-        // We have to implement this, so that if a potential matching method expects a Comparable, which is implemented
-        // by all the supported numerical types, the "Or" type will be a match. 
-        @Override
-        public int compareTo(Object o) {
-            Number n = getSourceNumber();
-            if (n instanceof Comparable) {
-                return ((Comparable) n).compareTo(o); 
-            } else {
-                throw new ClassCastException(n.getClass().getName() + " is not Comparable.");
-            }
-        }
-        
-    }
-
-    /**
-     * Holds a {@link BigDecimal} that stores a whole number. When selecting a overloaded method, FreeMarker tries to
-     * associate {@link BigDecimal} values to parameters of types that can hold non-whole numbers, unless the
-     * {@link BigDecimal} is wrapped into this class, in which case it does the opposite. This mechanism is, however,
-     * too rough to prevent roll overs or magnitude losses. Those are not yet handled for backward compatibility (they
-     * were suppressed earlier too).
-     */
-    static final class IntegerBigDecimal extends NumberWithFallbackType {
-
-        private final BigDecimal n;
-        
-        IntegerBigDecimal(BigDecimal n) {
-            this.n = n;
-        }
-
-        @Override
-        protected Number getSourceNumber() {
-            return n;
-        }
-        
-        public BigInteger bigIntegerValue() {
-            return n.toBigInteger();
-        }
-        
-    }
-
-    static abstract class LongOrSmallerInteger extends NumberWithFallbackType {
-        
-        private final Long n;
-        
-        protected LongOrSmallerInteger(Long n) {
-            this.n = n;
-        }
-
-        @Override
-        protected Number getSourceNumber() {
-            return n;
-        }
-
-        @Override
-        public long longValue() {
-            return n.longValue();
-        }
-        
-    }
-    
-    static class LongOrByte extends LongOrSmallerInteger {
-        
-        private final byte w; 
-
-        LongOrByte(Long n, byte w) {
-            super(n);
-            this.w = w;
-        }
-
-        @Override
-        public byte byteValue() {
-            return w;
-        }
-        
-    }
-    
-    static class LongOrShort extends LongOrSmallerInteger {
-        
-        private final short w; 
-
-        LongOrShort(Long n, short w) {
-            super(n);
-            this.w = w;
-        }
-
-        @Override
-        public short shortValue() {
-            return w;
-        }
-        
-    }
-    
-    static class LongOrInteger extends LongOrSmallerInteger {
-        
-        private final int w; 
-
-        LongOrInteger(Long n, int w) {
-            super(n);
-            this.w = w;
-        }
-
-        @Override
-        public int intValue() {
-            return w;
-        }
-        
-    }
-    
-    static abstract class IntegerOrSmallerInteger extends NumberWithFallbackType {
-        
-        private final Integer n;
-        
-        protected IntegerOrSmallerInteger(Integer n) {
-            this.n = n;
-        }
-
-        @Override
-        protected Number getSourceNumber() {
-            return n;
-        }
-
-        @Override
-        public int intValue() {
-            return n.intValue();
-        }
-        
-    }
-    
-    static class IntegerOrByte extends IntegerOrSmallerInteger {
-        
-        private final byte w; 
-
-        IntegerOrByte(Integer n, byte w) {
-            super(n);
-            this.w = w;
-        }
-
-        @Override
-        public byte byteValue() {
-            return w;
-        }
-        
-    }
-    
-    static class IntegerOrShort extends IntegerOrSmallerInteger {
-        
-        private final short w; 
-
-        IntegerOrShort(Integer n, short w) {
-            super(n);
-            this.w = w;
-        }
-
-        @Override
-        public short shortValue() {
-            return w;
-        }
-        
-    }
-    
-    static class ShortOrByte extends NumberWithFallbackType {
-        
-        private final Short n;
-        private final byte w;
-        
-        protected ShortOrByte(Short n, byte w) {
-            this.n = n;
-            this.w = w;
-        }
-
-        @Override
-        protected Number getSourceNumber() {
-            return n;
-        }
-
-        @Override
-        public short shortValue() {
-            return n.shortValue();
-        }
-
-        @Override
-        public byte byteValue() {
-            return w;
-        }
-        
-    }
-    
-    static abstract class DoubleOrWholeNumber extends NumberWithFallbackType {
-        
-        private final Double n; 
-
-        protected DoubleOrWholeNumber(Double n) {
-            this.n = n;
-        }
-
-        @Override
-        protected Number getSourceNumber() {
-            return n;
-        }
-        
-        @Override
-        public double doubleValue() {
-            return n.doubleValue();
-        }
-        
-    }
-    
-    static final class DoubleOrByte extends DoubleOrWholeNumber {
-        
-        private final byte w;
-
-        DoubleOrByte(Double n, byte w) {
-            super(n);
-            this.w = w;
-        }
-        
-        @Override
-        public byte byteValue() {
-            return w;
-        }
-        
-        @Override
-        public short shortValue() {
-            return w;
-        }
-        
-        @Override
-        public int intValue() {
-            return w;
-        }
-        
-        @Override
-        public long longValue() {
-            return w;
-        }
-        
-    }
-    
-    static final class DoubleOrShort extends DoubleOrWholeNumber {
-        
-        private final short w;
-
-        DoubleOrShort(Double n, short w) {
-            super(n);
-            this.w = w;
-        }
-        
-        @Override
-        public short shortValue() {
-            return w;
-        }
-        
-        @Override
-        public int intValue() {
-            return w;
-        }
-        
-        @Override
-        public long longValue() {
-            return w;
-        }
-        
-    }
-    
-    static final class DoubleOrIntegerOrFloat extends DoubleOrWholeNumber {
-
-        private final int w;
-
-        DoubleOrIntegerOrFloat(Double n, int w) {
-            super(n);
-            this.w = w;
-        }
-        
-        @Override
-        public int intValue() {
-            return w;
-        }
-        
-        @Override
-        public long longValue() {
-            return w;
-        }
-        
-    }
-    
-    static final class DoubleOrInteger extends DoubleOrWholeNumber {
-
-        private final int w;
-
-        DoubleOrInteger(Double n, int w) {
-            super(n);
-            this.w = w;
-        }
-        
-        @Override
-        public int intValue() {
-            return w;
-        }
-        
-        @Override
-        public long longValue() {
-            return w;
-        }
-        
-    }
-    
-    static final class DoubleOrLong extends DoubleOrWholeNumber {
-
-        private final long w;
-
-        DoubleOrLong(Double n, long w) {
-            super(n);
-            this.w = w;
-        }
-        
-        @Override
-        public long longValue() {
-            return w;
-        }
-        
-    }
-    
-    static final class DoubleOrFloat extends NumberWithFallbackType {
-        
-        private final Double n;
-
-        DoubleOrFloat(Double n) {
-            this.n = n;
-        }
-        
-        @Override
-        public float floatValue() {
-            return n.floatValue();
-        }
-        
-        @Override
-        public double doubleValue() {
-            return n.doubleValue();
-        }
-
-        @Override
-        protected Number getSourceNumber() {
-            return n;
-        }
-        
-    }
-
-    static abstract class FloatOrWholeNumber extends NumberWithFallbackType {
-        
-        private final Float n; 
-
-        FloatOrWholeNumber(Float n) {
-            this.n = n;
-        }
-
-        @Override
-        protected Number getSourceNumber() {
-            return n;
-        }
-        
-        @Override
-        public float floatValue() {
-            return n.floatValue();
-        }
-        
-    }
-    
-    static final class FloatOrByte extends FloatOrWholeNumber {
-        
-        private final byte w;
-
-        FloatOrByte(Float n, byte w) {
-            super(n);
-            this.w = w;
-        }
-        
-        @Override
-        public byte byteValue() {
-            return w;
-        }
-        
-        @Override
-        public short shortValue() {
-            return w;
-        }
-        
-        @Override
-        public int intValue() {
-            return w;
-        }
-        
-        @Override
-        public long longValue() {
-            return w;
-        }
-        
-    }
-    
-    static final class FloatOrShort extends FloatOrWholeNumber {
-        
-        private final short w;
-
-        FloatOrShort(Float n, short w) {
-            super(n);
-            this.w = w;
-        }
-        
-        @Override
-        public short shortValue() {
-            return w;
-        }
-        
-        @Override
-        public int intValue() {
-            return w;
-        }
-        
-        @Override
-        public long longValue() {
-            return w;
-        }
-        
-    }
-
-    static final class FloatOrInteger extends FloatOrWholeNumber {
-        
-        private final int w;
-
-        FloatOrInteger(Float n, int w) {
-            super(n);
-            this.w = w;
-        }
-        
-        @Override
-        public int intValue() {
-            return w;
-        }
-        
-        @Override
-        public long longValue() {
-            return w;
-        }
-        
-    }
-
-    abstract static class BigIntegerOrPrimitive extends NumberWithFallbackType {
-
-        protected final BigInteger n;
-        
-        BigIntegerOrPrimitive(BigInteger n) {
-            this.n = n;
-        }
-
-        @Override
-        protected Number getSourceNumber() {
-            return n;
-        }
-        
-    }
-    
-    final static class BigIntegerOrByte extends BigIntegerOrPrimitive {
-
-        BigIntegerOrByte(BigInteger n) {
-            super(n);
-        }
-
-    }
-    
-    final static class BigIntegerOrShort extends BigIntegerOrPrimitive {
-
-        BigIntegerOrShort(BigInteger n) {
-            super(n);
-        }
-
-    }
-    
-    final static class BigIntegerOrInteger extends BigIntegerOrPrimitive {
-
-        BigIntegerOrInteger(BigInteger n) {
-            super(n);
-        }
-
-    }
-    
-    final static class BigIntegerOrLong extends BigIntegerOrPrimitive {
-
-        BigIntegerOrLong(BigInteger n) {
-            super(n);
-        }
-
-    }
-
-    abstract static class BigIntegerOrFPPrimitive extends BigIntegerOrPrimitive {
-
-        BigIntegerOrFPPrimitive(BigInteger n) {
-            super(n);
-        }
-
-        /** Faster version of {@link BigDecimal#floatValue()}, utilizes that the number known to fit into a long. */
-        @Override
-        public float floatValue() {
-            return n.longValue(); 
-        }
-        
-        /** Faster version of {@link BigDecimal#doubleValue()}, utilizes that the number known to fit into a long. */
-        @Override
-        public double doubleValue() {
-            return n.longValue(); 
-        }
-
-    }
-    
-    final static class BigIntegerOrFloat extends BigIntegerOrFPPrimitive {
-
-        BigIntegerOrFloat(BigInteger n) {
-            super(n);
-        }
-
-    }
-    
-    final static class BigIntegerOrDouble extends BigIntegerOrFPPrimitive {
-
-        BigIntegerOrDouble(BigInteger n) {
-            super(n);
-        }
-        
-    }
-    
-    /**
-     * Returns a non-negative number that indicates how much we want to avoid a given numerical type conversion. Since
-     * we only consider the types here, not the actual value, we always consider the worst case scenario. Like it will
-     * say that converting int to short is not allowed, although int 1 can be converted to byte without loss. To account
-     * for such situations, "Or"-ed types, like {@link IntegerOrByte} has to be used. 
-     * 
-     * @param fromC the non-primitive type of the argument (with other words, the actual type).
-     *        Must be {@link Number} or its subclass. This is possibly an {@link NumberWithFallbackType} subclass.
-     * @param toC the <em>non-primitive</em> type of the target parameter (with other words, the format type).
-     *        Must be a {@link Number} subclass, not {@link Number} itself.
-     *        Must <em>not</em> be {@link NumberWithFallbackType} or its subclass.
-     * 
-     * @return
-     *     <p>The possible values are:
-     *     <ul>
-     *       <li>0: No conversion is needed
-     *       <li>[0, 30000): Lossless conversion
-     *       <li>[30000, 40000): Smaller precision loss in mantissa is possible.
-     *       <li>[40000, 50000): Bigger precision loss in mantissa is possible.
-     *       <li>{@link Integer#MAX_VALUE}: Conversion not allowed due to the possibility of magnitude loss or
-     *          overflow</li>
-     *     </ul>
-     * 
-     *     <p>At some places, we only care if the conversion is possible, i.e., whether the return value is
-     *     {@link Integer#MAX_VALUE} or not. But when multiple overloaded methods have an argument type to which we
-     *     could convert to, this number will influence which of those will be chosen.
-     */
-    static int getArgumentConversionPrice(Class fromC, Class toC) {
-        // DO NOT EDIT, generated code!
-        // See: src\main\misc\overloadedNumberRules\README.txt
-        if (toC == fromC) {
-            return 0;
-        } else if (toC == Integer.class) {
-            if (fromC == IntegerBigDecimal.class) return 31003;
-            else if (fromC == BigDecimal.class) return 41003;
-            else if (fromC == Long.class) return Integer.MAX_VALUE;
-            else if (fromC == Double.class) return Integer.MAX_VALUE;
-            else if (fromC == Float.class) return Integer.MAX_VALUE;
-            else if (fromC == Byte.class) return 10003;
-            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == LongOrInteger.class) return 21003;
-            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrIntegerOrFloat.class) return 22003;
-            else if (fromC == DoubleOrInteger.class) return 22003;
-            else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
-            else if (fromC == IntegerOrByte.class) return 0;
-            else if (fromC == DoubleOrByte.class) return 22003;
-            else if (fromC == LongOrByte.class) return 21003;
-            else if (fromC == Short.class) return 10003;
-            else if (fromC == LongOrShort.class) return 21003;
-            else if (fromC == ShortOrByte.class) return 10003;
-            else if (fromC == FloatOrInteger.class) return 21003;
-            else if (fromC == FloatOrByte.class) return 21003;
-            else if (fromC == FloatOrShort.class) return 21003;
-            else if (fromC == BigIntegerOrInteger.class) return 16003;
-            else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrByte.class) return 16003;
-            else if (fromC == IntegerOrShort.class) return 0;
-            else if (fromC == DoubleOrShort.class) return 22003;
-            else if (fromC == BigIntegerOrShort.class) return 16003;
-            else return Integer.MAX_VALUE;
-        } else if (toC == Long.class) {
-            if (fromC == Integer.class) return 10004;
-            else if (fromC == IntegerBigDecimal.class) return 31004;
-            else if (fromC == BigDecimal.class) return 41004;
-            else if (fromC == Double.class) return Integer.MAX_VALUE;
-            else if (fromC == Float.class) return Integer.MAX_VALUE;
-            else if (fromC == Byte.class) return 10004;
-            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == LongOrInteger.class) return 0;
-            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrIntegerOrFloat.class) return 21004;
-            else if (fromC == DoubleOrInteger.class) return 21004;
-            else if (fromC == DoubleOrLong.class) return 21004;
-            else if (fromC == IntegerOrByte.class) return 10004;
-            else if (fromC == DoubleOrByte.class) return 21004;
-            else if (fromC == LongOrByte.class) return 0;
-            else if (fromC == Short.class) return 10004;
-            else if (fromC == LongOrShort.class) return 0;
-            else if (fromC == ShortOrByte.class) return 10004;
-            else if (fromC == FloatOrInteger.class) return 21004;
-            else if (fromC == FloatOrByte.class) return 21004;
-            else if (fromC == FloatOrShort.class) return 21004;
-            else if (fromC == BigIntegerOrInteger.class) return 15004;
-            else if (fromC == BigIntegerOrLong.class) return 15004;
-            else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrByte.class) return 15004;
-            else if (fromC == IntegerOrShort.class) return 10004;
-            else if (fromC == DoubleOrShort.class) return 21004;
-            else if (fromC == BigIntegerOrShort.class) return 15004;
-            else return Integer.MAX_VALUE;
-        } else if (toC == Double.class) {
-            if (fromC == Integer.class) return 20007;
-            else if (fromC == IntegerBigDecimal.class) return 32007;
-            else if (fromC == BigDecimal.class) return 32007;
-            else if (fromC == Long.class) return 30007;
-            else if (fromC == Float.class) return 10007;
-            else if (fromC == Byte.class) return 20007;
-            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == LongOrInteger.class) return 21007;
-            else if (fromC == DoubleOrFloat.class) return 0;
-            else if (fromC == DoubleOrIntegerOrFloat.class) return 0;
-            else if (fromC == DoubleOrInteger.class) return 0;
-            else if (fromC == DoubleOrLong.class) return 0;
-            else if (fromC == IntegerOrByte.class) return 20007;
-            else if (fromC == DoubleOrByte.class) return 0;
-            else if (fromC == LongOrByte.class) return 21007;
-            else if (fromC == Short.class) return 20007;
-            else if (fromC == LongOrShort.class) return 21007;
-            else if (fromC == ShortOrByte.class) return 20007;
-            else if (fromC == FloatOrInteger.class) return 10007;
-            else if (fromC == FloatOrByte.class) return 10007;
-            else if (fromC == FloatOrShort.class) return 10007;
-            else if (fromC == BigIntegerOrInteger.class) return 20007;
-            else if (fromC == BigIntegerOrLong.class) return 30007;
-            else if (fromC == BigIntegerOrDouble.class) return 20007;
-            else if (fromC == BigIntegerOrFloat.class) return 20007;
-            else if (fromC == BigIntegerOrByte.class) return 20007;
-            else if (fromC == IntegerOrShort.class) return 20007;
-            else if (fromC == DoubleOrShort.class) return 0;
-            else if (fromC == BigIntegerOrShort.class) return 20007;
-            else return Integer.MAX_VALUE;
-        } else if (toC == Float.class) {
-            if (fromC == Integer.class) return 30006;
-            else if (fromC == IntegerBigDecimal.class) return 33006;
-            else if (fromC == BigDecimal.class) return 33006;
-            else if (fromC == Long.class) return 40006;
-            else if (fromC == Double.class) return Integer.MAX_VALUE;
-            else if (fromC == Byte.class) return 20006;
-            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == LongOrInteger.class) return 30006;
-            else if (fromC == DoubleOrFloat.class) return 30006;
-            else if (fromC == DoubleOrIntegerOrFloat.class) return 23006;
-            else if (fromC == DoubleOrInteger.class) return 30006;
-            else if (fromC == DoubleOrLong.class) return 40006;
-            else if (fromC == IntegerOrByte.class) return 24006;
-            else if (fromC == DoubleOrByte.class) return 23006;
-            else if (fromC == LongOrByte.class) return 24006;
-            else if (fromC == Short.class) return 20006;
-            else if (fromC == LongOrShort.class) return 24006;
-            else if (fromC == ShortOrByte.class) return 20006;
-            else if (fromC == FloatOrInteger.class) return 0;
-            else if (fromC == FloatOrByte.class) return 0;
-            else if (fromC == FloatOrShort.class) return 0;
-            else if (fromC == BigIntegerOrInteger.class) return 30006;
-            else if (fromC == BigIntegerOrLong.class) return 40006;
-            else if (fromC == BigIntegerOrDouble.class) return 40006;
-            else if (fromC == BigIntegerOrFloat.class) return 24006;
-            else if (fromC == BigIntegerOrByte.class) return 24006;
-            else if (fromC == IntegerOrShort.class) return 24006;
-            else if (fromC == DoubleOrShort.class) return 23006;
-            else if (fromC == BigIntegerOrShort.class) return 24006;
-            else return Integer.MAX_VALUE;
-        } else if (toC == Byte.class) {
-            if (fromC == Integer.class) return Integer.MAX_VALUE;
-            else if (fromC == IntegerBigDecimal.class) return 35001;
-            else if (fromC == BigDecimal.class) return 45001;
-            else if (fromC == Long.class) return Integer.MAX_VALUE;
-            else if (fromC == Double.class) return Integer.MAX_VALUE;
-            else if (fromC == Float.class) return Integer.MAX_VALUE;
-            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == LongOrInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrIntegerOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
-            else if (fromC == IntegerOrByte.class) return 22001;
-            else if (fromC == DoubleOrByte.class) return 25001;
-            else if (fromC == LongOrByte.class) return 23001;
-            else if (fromC == Short.class) return Integer.MAX_VALUE;
-            else if (fromC == LongOrShort.class) return Integer.MAX_VALUE;
-            else if (fromC == ShortOrByte.class) return 21001;
-            else if (fromC == FloatOrInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == FloatOrByte.class) return 23001;
-            else if (fromC == FloatOrShort.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrByte.class) return 18001;
-            else if (fromC == IntegerOrShort.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrShort.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrShort.class) return Integer.MAX_VALUE;
-            else return Integer.MAX_VALUE;
-        } else if (toC == Short.class) {
-            if (fromC == Integer.class) return Integer.MAX_VALUE;
-            else if (fromC == IntegerBigDecimal.class) return 34002;
-            else if (fromC == BigDecimal.class) return 44002;
-            else if (fromC == Long.class) return Integer.MAX_VALUE;
-            else if (fromC == Double.class) return Integer.MAX_VALUE;
-            else if (fromC == Float.class) return Integer.MAX_VALUE;
-            else if (fromC == Byte.class) return 10002;
-            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == LongOrInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrIntegerOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
-            else if (fromC == IntegerOrByte.class) return 21002;
-            else if (fromC == DoubleOrByte.class) return 24002;
-            else if (fromC == LongOrByte.class) return 22002;
-            else if (fromC == LongOrShort.class) return 22002;
-            else if (fromC == ShortOrByte.class) return 0;
-            else if (fromC == FloatOrInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == FloatOrByte.class) return 22002;
-            else if (fromC == FloatOrShort.class) return 22002;
-            else if (fromC == BigIntegerOrInteger.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == BigIntegerOrByte.class) return 17002;
-            else if (fromC == IntegerOrShort.class) return 21002;
-            else if (fromC == DoubleOrShort.class) return 24002;
-            else if (fromC == BigIntegerOrShort.class) return 17002;
-            else return Integer.MAX_VALUE;
-        } else if (toC == BigDecimal.class) {
-            if (fromC == Integer.class) return 20008;
-            else if (fromC == IntegerBigDecimal.class) return 0;
-            else if (fromC == Long.class) return 20008;
-            else if (fromC == Double.class) return 20008;
-            else if (fromC == Float.class) return 20008;
-            else if (fromC == Byte.class) return 20008;
-            else if (fromC == BigInteger.class) return 10008;
-            else if (fromC == LongOrInteger.class) return 20008;
-            else if (fromC == DoubleOrFloat.class) return 20008;
-            else if (fromC == DoubleOrIntegerOrFloat.class) return 20008;
-            else if (fromC == DoubleOrInteger.class) return 20008;
-            else if (fromC == DoubleOrLong.class) return 20008;
-            else if (fromC == IntegerOrByte.class) return 20008;
-            else if (fromC == DoubleOrByte.class) return 20008;
-            else if (fromC == LongOrByte.class) return 20008;
-            else if (fromC == Short.class) return 20008;
-            else if (fromC == LongOrShort.class) return 20008;
-            else if (fromC == ShortOrByte.class) return 20008;
-            else if (fromC == FloatOrInteger.class) return 20008;
-            else if (fromC == FloatOrByte.class) return 20008;
-            else if (fromC == FloatOrShort.class) return 20008;
-            else if (fromC == BigIntegerOrInteger.class) return 10008;
-            else if (fromC == BigIntegerOrLong.class) return 10008;
-            else if (fromC == BigIntegerOrDouble.class) return 10008;
-            else if (fromC == BigIntegerOrFloat.class) return 10008;
-            else if (fromC == BigIntegerOrByte.class) return 10008;
-            else if (fromC == IntegerOrShort.class) return 20008;
-            else if (fromC == DoubleOrShort.class) return 20008;
-            else if (fromC == BigIntegerOrShort.class) return 10008;
-            else return Integer.MAX_VALUE;
-        } else if (toC == BigInteger.class) {
-            if (fromC == Integer.class) return 10005;
-            else if (fromC == IntegerBigDecimal.class) return 10005;
-            else if (fromC == BigDecimal.class) return 40005;
-            else if (fromC == Long.class) return 10005;
-            else if (fromC == Double.class) return Integer.MAX_VALUE;
-            else if (fromC == Float.class) return Integer.MAX_VALUE;
-            else if (fromC == Byte.class) return 10005;
-            else if (fromC == LongOrInteger.class) return 10005;
-            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
-            else if (fromC == DoubleOrIntegerOrFloat.class) return 21005;
-            else if (fromC == DoubleOrInteger.class) return 21005;
-            else if (fromC == DoubleOrLong.class) return 21005;
-            else if (fromC == IntegerOrByte.class) return 10005;
-            else if (fromC == DoubleOrByte.class) return 21005;
-            else if (fromC == LongOrByte.class) return 10005;
-            else if (fromC == Short.class) return 10005;
-            else if (fromC == LongOrShort.class) return 10005;
-            else if (fromC == ShortOrByte.class) return 10005;
-            else if (fromC == FloatOrInteger.class) return 25005;
-            else if (fromC == FloatOrByte.class) return 25005;
-            else if (fromC == FloatOrShort.class) return 25005;
-            else if (fromC == BigIntegerOrInteger.class) return 0;
-            else if (fromC == BigIntegerOrLong.class) return 0;
-            else if (fromC == BigIntegerOrDouble.class) return 0;
-            else if (fromC == BigIntegerOrFloat.class) return 0;
-            else if (fromC == BigIntegerOrByte.class) return 0;
-            else if (fromC == IntegerOrShort.class) return 10005;
-            else if (fromC == DoubleOrShort.class) return 21005;
-            else if (fromC == BigIntegerOrShort.class) return 0;
-            else return Integer.MAX_VALUE;
-        } else {
-            // Unknown toC; we don't know how to convert to it:
-            return Integer.MAX_VALUE;
-        }        
-    }
-
-    static int compareNumberTypeSpecificity(Class c1, Class c2) {
-        // DO NOT EDIT, generated code!
-        // See: src\main\misc\overloadedNumberRules\README.txt
-        c1 = _ClassUtil.primitiveClassToBoxingClass(c1);
-        c2 = _ClassUtil.primitiveClassToBoxingClass(c2);
-        
-        if (c1 == c2) return 0;
-        
-        if (c1 == Integer.class) {
-            if (c2 == Long.class) return 4 - 3;
-            if (c2 == Double.class) return 7 - 3;
-            if (c2 == Float.class) return 6 - 3;
-            if (c2 == Byte.class) return 1 - 3;
-            if (c2 == Short.class) return 2 - 3;
-            if (c2 == BigDecimal.class) return 8 - 3;
-            if (c2 == BigInteger.class) return 5 - 3;
-            return 0;
-        }
-        if (c1 == Long.class) {
-            if (c2 == Integer.class) return 3 - 4;
-            if (c2 == Double.class) return 7 - 4;
-            if (c2 == Float.class) return 6 - 4;
-            if (c2 == Byte.class) return 1 - 4;
-            if (c2 == Short.class) return 2 - 4;
-            if (c2 == BigDecimal.class) return 8 - 4;
-            if (c2 == BigInteger.class) return 5 - 4;
-            return 0;
-        }
-        if (c1 == Double.class) {
-            if (c2 == Integer.class) return 3 - 7;
-            if (c2 == Long.class) return 4 - 7;
-            if (c2 == Float.class) return 6 - 7;
-            if (c2 == Byte.class) return 1 - 7;
-            if (c2 == Short.class) return 2 - 7;
-            if (c2 == BigDecimal.class) return 8 - 7;
-            if (c2 == BigInteger.class) return 5 - 7;
-            return 0;
-        }
-        if (c1 == Float.class) {
-            if (c2 == Integer.class) return 3 - 6;
-            if (c2 == Long.class) return 4 - 6;
-            if (c2 == Double.class) return 7 - 6;
-            if (c2 == Byte.class) return 1 - 6;
-            if (c2 == Short.class) return 2 - 6;
-            if (c2 == BigDecimal.class) return 8 - 6;
-            if (c2 == BigInteger.class) return 5 - 6;
-            return 0;
-        }
-        if (c1 == Byte.class) {
-            if (c2 == Integer.class) return 3 - 1;
-            if (c2 == Long.class) return 4 - 1;
-            if (c2 == Double.class) return 7 - 1;
-            if (c2 == Float.class) return 6 - 1;
-            if (c2 == Short.class) return 2 - 1;
-            if (c2 == BigDecimal.class) return 8 - 1;
-            if (c2 == BigInteger.class) return 5 - 1;
-            return 0;
-        }
-        if (c1 == Short.class) {
-            if (c2 == Integer.class) return 3 - 2;
-            if (c2 == Long.class) return 4 - 2;
-            if (c2 == Double.class) return 7 - 2;
-            if (c2 == Float.class) return 6 - 2;
-            if (c2 == Byte.class) return 1 - 2;
-            if (c2 == BigDecimal.class) return 8 - 2;
-            if (c2 == BigInteger.class) return 5 - 2;
-            return 0;
-        }
-        if (c1 == BigDecimal.class) {
-            if (c2 == Integer.class) return 3 - 8;
-            if (c2 == Long.class) return 4 - 8;
-            if (c2 == Double.class) return 7 - 8;
-            if (c2 == Float.class) return 6 - 8;
-            if (c2 == Byte.class) return 1 - 8;
-            if (c2 == Short.class) return 2 - 8;
-            if (c2 == BigInteger.class) return 5 - 8;
-            return 0;
-        }
-        if (c1 == BigInteger.class) {
-            if (c2 == Integer.class) return 3 - 5;
-            if (c2 == Long.class) return 4 - 5;
-            if (c2 == Double.class) return 7 - 5;
-            if (c2 == Float.class) return 6 - 5;
-            if (c2 == Byte.class) return 1 - 5;
-            if (c2 == Short.class) return 2 - 5;
-            if (c2 == BigDecimal.class) return 8 - 5;
-            return 0;
-        }
-        return 0;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedVarArgsMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedVarArgsMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedVarArgsMethods.java
deleted file mode 100644
index 4a6d277..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedVarArgsMethods.java
+++ /dev/null
@@ -1,245 +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.beans;
-
-import java.lang.reflect.Array;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.util.BugException;
-
-/**
- * Stores the varargs methods for a {@link OverloadedMethods} object.
- */
-class OverloadedVarArgsMethods extends OverloadedMethodsSubset {
-
-    OverloadedVarArgsMethods() {
-        super();
-    }
-    
-    /**
-     * Replaces the last parameter type with the array component type of it.
-     */
-    @Override
-    Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) {
-        final Class[] preprocessedParamTypes = memberDesc.getParamTypes().clone();
-        int ln = preprocessedParamTypes.length;
-        final Class varArgsCompType = preprocessedParamTypes[ln - 1].getComponentType();
-        if (varArgsCompType == null) {
-            throw new BugException("Only varargs methods should be handled here");
-        }
-        preprocessedParamTypes[ln - 1] = varArgsCompType;
-        return preprocessedParamTypes;
-    }
-
-    @Override
-    void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) {
-        // Overview
-        // --------
-        //
-        // So far, m(t1, t2...) was treated by the hint widening like m(t1, t2). So now we have to continue hint
-        // widening like if we had further methods:
-        // - m(t1, t2, t2), m(t1, t2, t2, t2), ...
-        // - 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.
-        // So we only update the already existing hints. Remember that we already have m(t1, t2) there.
-        
-        final int paramCount = paramTypes.length;
-        final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();
-        
-        // The case of e(t1, t2), e(t1, t2, t2), e(t1, t2, t2, t2), ..., where e is an *earlierly* added method.
-        // When that was added, this method wasn't added yet, so it had no chance updating the hints of this method,
-        // so we do that now:
-        // FIXME: Only needed if m(t1, t2) was filled an empty slot, otherwise whatever was there was already
-        // widened by the preceding hints, so this will be a no-op.
-        for (int i = paramCount - 1; i >= 0; i--) {
-            final Class[] previousHints = unwrappingHintsByParamCount[i];
-            if (previousHints != null) {
-                widenHintsToCommonSupertypes(
-                        paramCount,
-                        previousHints, getTypeFlags(i));
-                break;  // we only do this for the first hit, as the methods before that has already widened it.
-            }
-        }
-        // The case of e(t1), where e is an *earlier* added method.
-        // When that was added, this method wasn't added yet, so it had no chance updating the hints of this method,
-        // so we do that now:
-        // FIXME: Same as above; it's often unnecessary.
-        if (paramCount + 1 < unwrappingHintsByParamCount.length) {
-            Class[] oneLongerHints = unwrappingHintsByParamCount[paramCount + 1];
-            if (oneLongerHints != null) {
-                widenHintsToCommonSupertypes(
-                        paramCount,
-                        oneLongerHints, getTypeFlags(paramCount + 1));
-            }
-        }
-        
-        // The case of m(t1, t2, t2), m(t1, t2, t2, t2), ..., where m is the currently added method.
-        // Update the longer hints-arrays:  
-        for (int i = paramCount + 1; i < unwrappingHintsByParamCount.length; i++) {
-            widenHintsToCommonSupertypes(
-                    i,
-                    paramTypes, paramNumericalTypes);
-        }
-        // The case of m(t1) where m is the currently added method.
-        // update the one-shorter hints-array:  
-        if (paramCount > 0) {  // (should be always true, or else it wasn't a varags method)
-            widenHintsToCommonSupertypes(
-                    paramCount - 1,
-                    paramTypes, paramNumericalTypes);
-        }
-        
-    }
-    
-    private void widenHintsToCommonSupertypes(
-            int paramCountOfWidened, Class[] wideningTypes, int[] wideningTypeFlags) {
-        final Class[] typesToWiden = getUnwrappingHintsByParamCount()[paramCountOfWidened];
-        if (typesToWiden == null) { 
-            return;  // no such overload exists; nothing to widen
-        }
-        
-        final int typesToWidenLen = typesToWiden.length;
-        final int wideningTypesLen = wideningTypes.length;
-        int min = Math.min(wideningTypesLen, typesToWidenLen);
-        for (int i = 0; i < min; ++i) {
-            typesToWiden[i] = getCommonSupertypeForUnwrappingHint(typesToWiden[i], wideningTypes[i]);
-        }
-        if (typesToWidenLen > wideningTypesLen) {
-            Class varargsComponentType = wideningTypes[wideningTypesLen - 1];
-            for (int i = wideningTypesLen; i < typesToWidenLen; ++i) {
-                typesToWiden[i] = getCommonSupertypeForUnwrappingHint(typesToWiden[i], varargsComponentType);
-            }
-        }
-        
-        mergeInTypesFlags(paramCountOfWidened, wideningTypeFlags);
-    }
-    
-    @Override
-    MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, BeansWrapper unwrapper) 
-    throws TemplateModelException {
-        if (tmArgs == null) {
-            // null is treated as empty args
-            tmArgs = Collections.EMPTY_LIST;
-        }
-        final int argsLen = tmArgs.size();
-        final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();
-        final Object[] pojoArgs = new Object[argsLen];
-        int[] typesFlags = null;
-        // Going down starting from methods with args.length + 1 parameters, because we must try to match against a case
-        // where all specified args are fixargs, and we have 0 varargs.
-        outer: for (int paramCount = Math.min(argsLen + 1, unwrappingHintsByParamCount.length - 1); paramCount >= 0; --paramCount) {
-            Class[] unwarappingHints = unwrappingHintsByParamCount[paramCount];
-            if (unwarappingHints == null) {
-                if (paramCount == 0) {
-                    return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS;
-                }
-                continue;
-            }
-            
-            typesFlags = getTypeFlags(paramCount);
-            if (typesFlags == ALL_ZEROS_ARRAY) {
-                typesFlags = null;
-            }
-            
-            // Try to unwrap the arguments
-            Iterator it = tmArgs.iterator();
-            for (int i = 0; i < argsLen; ++i) {
-                int paramIdx = i < paramCount ? i : paramCount - 1;
-                Object pojo = unwrapper.tryUnwrapTo(
-                        (TemplateModel) it.next(),
-                        unwarappingHints[paramIdx],
-                        typesFlags != null ? typesFlags[paramIdx] : 0);
-                if (pojo == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-                    continue outer;
-                }
-                pojoArgs[i] = pojo;
-            }
-            break outer;
-        }
-        
-        MaybeEmptyCallableMemberDescriptor maybeEmtpyMemberDesc = getMemberDescriptorForArgs(pojoArgs, true);
-        if (maybeEmtpyMemberDesc instanceof CallableMemberDescriptor) {
-            CallableMemberDescriptor memberDesc = (CallableMemberDescriptor) maybeEmtpyMemberDesc;
-            Object[] pojoArgsWithArray;
-            Object argsOrErrorIdx = replaceVarargsSectionWithArray(pojoArgs, tmArgs, memberDesc, unwrapper);
-            if (argsOrErrorIdx instanceof Object[]) {
-                pojoArgsWithArray = (Object[]) argsOrErrorIdx;
-            } else {
-                return EmptyMemberAndArguments.noCompatibleOverload(((Integer) argsOrErrorIdx).intValue());
-            }
-            if (typesFlags != null) {
-                // Note that overloaded method selection has already accounted for overflow errors when the method
-                // was selected. So this forced conversion shouldn't cause such corruption. Except, conversion from
-                // BigDecimal is allowed to overflow for backward-compatibility.
-                forceNumberArgumentsToParameterTypes(pojoArgsWithArray, memberDesc.getParamTypes(), typesFlags);
-            }
-            return new MemberAndArguments(memberDesc, pojoArgsWithArray);
-        } else {
-            return EmptyMemberAndArguments.from((EmptyCallableMemberDescriptor) maybeEmtpyMemberDesc, pojoArgs);
-        }
-    }
-    
-    /**
-     * Converts a flat argument list to one where the last argument is an array that collects the varargs, also
-     * re-unwraps the varargs to the component type. Note that this couldn't be done until we had the concrete
-     * member selected.
-     * 
-     * @return An {@code Object[]} if everything went well, or an {@code Integer} the
-     *    order (1-based index) of the argument that couldn't be unwrapped. 
-     */
-    private Object replaceVarargsSectionWithArray(
-            Object[] args, List modelArgs, CallableMemberDescriptor memberDesc, BeansWrapper unwrapper) 
-    throws TemplateModelException {
-        final Class[] paramTypes = memberDesc.getParamTypes();
-        final int paramCount = paramTypes.length;
-        final Class varArgsCompType = paramTypes[paramCount - 1].getComponentType(); 
-        final int totalArgCount = args.length;
-        final int fixArgCount = paramCount - 1;
-        if (args.length != paramCount) {
-            Object[] packedArgs = new Object[paramCount];
-            System.arraycopy(args, 0, packedArgs, 0, fixArgCount);
-            Object varargs = Array.newInstance(varArgsCompType, totalArgCount - fixArgCount);
-            for (int i = fixArgCount; i < totalArgCount; ++i) {
-                Object val = unwrapper.tryUnwrapTo((TemplateModel) modelArgs.get(i), varArgsCompType);
-                if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-                    return Integer.valueOf(i + 1);
-                }
-                Array.set(varargs, i - fixArgCount, val);
-            }
-            packedArgs[fixArgCount] = varargs;
-            return packedArgs;
-        } else {
-            Object val = unwrapper.tryUnwrapTo((TemplateModel) modelArgs.get(fixArgCount), varArgsCompType);
-            if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-                return Integer.valueOf(fixArgCount + 1);
-            }
-            Object array = Array.newInstance(varArgsCompType, 1);
-            Array.set(array, 0, val);
-            args[fixArgCount] = array;
-            return args;
-        }
-    }
-    
-}
\ No newline at end of file



[14/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java b/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java
new file mode 100644
index 0000000..633687d
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.freemarker.core._DelayedConversionToString;
+import org.apache.freemarker.core._DelayedJQuote;
+import org.apache.freemarker.core._TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._ClassUtil;
+
+/**
+ * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ */
+public final class _MethodUtil {
+    
+    private _MethodUtil() {
+        // Not meant to be instantiated
+    }
+
+    /**
+     * Determines whether the type given as the 1st argument is convertible to the type given as the 2nd argument
+     * for method call argument conversion. This follows the rules of the Java reflection-based method call, except
+     * that since we don't have the value here, a boxed class is never seen as convertible to a primitive type. 
+     * 
+     * @return 0 means {@code false}, non-0 means {@code true}.
+     *         That is, 0 is returned less specificity or incomparable specificity, also when if
+     *         then method was aborted because of {@code ifHigherThan}.
+     *         The absolute value of the returned non-0 number symbolizes how more specific it is:
+     *         <ul>
+     *           <li>1: The two classes are identical</li>
+     *           <li>2: The 1st type is primitive, the 2nd type is the corresponding boxing class</li>
+     *           <li>3: Both classes are numerical, and one is convertible into the other with widening conversion.
+     *                  E.g., {@code int} is convertible to {@code long} and {#code double}, hence {@code int} is more
+     *                  specific.
+     *                  This ignores primitive VS boxed mismatches, except that a boxed class is never seen as
+     *                  convertible to a primitive class.</li>
+     *           <li>4: One class is {@code instanceof} of the other, but they aren't identical.
+     *               But unlike in Java, primitive numerical types are {@code instanceof} {@link Number} here.</li>
+     *         </ul> 
+     */
+    // TODO Seems that we don't use the full functionality of this anymore, so we could simplify this. See usages.
+    public static int isMoreOrSameSpecificParameterType(final Class specific, final Class generic, boolean bugfixed,
+            int ifHigherThan) {
+        if (ifHigherThan >= 4) return 0;
+        if (generic.isAssignableFrom(specific)) {
+            // Identity or widening reference conversion:
+            return generic == specific ? 1 : 4;
+        } else {
+            final boolean specificIsPrim = specific.isPrimitive(); 
+            final boolean genericIsPrim = generic.isPrimitive();
+            if (specificIsPrim) {
+                if (genericIsPrim) {
+                    if (ifHigherThan >= 3) return 0;
+                    return isWideningPrimitiveNumberConversion(specific, generic) ? 3 : 0;
+                } else {  // => specificIsPrim && !genericIsPrim
+                    if (bugfixed) {
+                        final Class specificAsBoxed = _ClassUtil.primitiveClassToBoxingClass(specific);
+                        if (specificAsBoxed == generic) {
+                            // A primitive class is more specific than its boxing class, because it can't store null
+                            return 2;
+                        } else if (generic.isAssignableFrom(specificAsBoxed)) {
+                            // Note: This only occurs if `specific` is a primitive numerical, and `generic == Number`
+                            return 4;
+                        } else if (ifHigherThan >= 3) {
+                            return 0;
+                        } else if (Number.class.isAssignableFrom(specificAsBoxed)
+                                && Number.class.isAssignableFrom(generic)) {
+                            return isWideningBoxedNumberConversion(specificAsBoxed, generic) ? 3 : 0;
+                        } else {
+                            return 0;
+                        }
+                    } else {
+                        return 0;
+                    }
+                }
+            } else {  // => !specificIsPrim
+                if (ifHigherThan >= 3) return 0;
+                if (bugfixed && !genericIsPrim
+                        && Number.class.isAssignableFrom(specific) && Number.class.isAssignableFrom(generic)) {
+                    return isWideningBoxedNumberConversion(specific, generic) ? 3 : 0;
+                } else {
+                    return 0;
+                }
+            }
+        }  // of: !generic.isAssignableFrom(specific) 
+    }
+
+    private static boolean isWideningPrimitiveNumberConversion(final Class source, final Class target) {
+        if (target == Short.TYPE && (source == Byte.TYPE)) {
+            return true;
+        } else if (target == Integer.TYPE && 
+           (source == Short.TYPE || source == Byte.TYPE)) {
+            return true;
+        } else if (target == Long.TYPE && 
+           (source == Integer.TYPE || source == Short.TYPE || 
+            source == Byte.TYPE)) {
+            return true;
+        } else if (target == Float.TYPE && 
+           (source == Long.TYPE || source == Integer.TYPE || 
+            source == Short.TYPE || source == Byte.TYPE)) {
+            return true;
+        } else if (target == Double.TYPE && 
+           (source == Float.TYPE || source == Long.TYPE || 
+            source == Integer.TYPE || source == Short.TYPE || 
+            source == Byte.TYPE)) {
+            return true; 
+        } else {
+            return false;
+        }
+    }
+
+    private static boolean isWideningBoxedNumberConversion(final Class source, final Class target) {
+        if (target == Short.class && source == Byte.class) {
+            return true;
+        } else if (target == Integer.class && 
+           (source == Short.class || source == Byte.class)) {
+            return true;
+        } else if (target == Long.class && 
+           (source == Integer.class || source == Short.class || 
+            source == Byte.class)) {
+            return true;
+        } else if (target == Float.class && 
+           (source == Long.class || source == Integer.class || 
+            source == Short.class || source == Byte.class)) {
+            return true;
+        } else if (target == Double.class && 
+           (source == Float.class || source == Long.class || 
+            source == Integer.class || source == Short.class || 
+            source == Byte.class)) {
+            return true; 
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Attention, this doesn't handle primitive classes correctly, nor numerical conversions.
+     */
+    public static Set getAssignables(Class c1, Class c2) {
+        Set s = new HashSet();
+        collectAssignables(c1, c2, s);
+        return s;
+    }
+    
+    private static void collectAssignables(Class c1, Class c2, Set s) {
+        if (c1.isAssignableFrom(c2)) {
+            s.add(c1);
+        }
+        Class sc = c1.getSuperclass();
+        if (sc != null) {
+            collectAssignables(sc, c2, s);
+        }
+        Class[] itf = c1.getInterfaces();
+        for (Class anItf : itf) {
+            collectAssignables(anItf, c2, s);
+        }
+    }
+
+    public static Class[] getParameterTypes(Member member) {
+        if (member instanceof Method) {
+            return ((Method) member).getParameterTypes();
+        }
+        if (member instanceof Constructor) {
+            return ((Constructor) member).getParameterTypes();
+        }
+        throw new IllegalArgumentException("\"member\" must be Method or Constructor");
+    }
+
+    public static boolean isVarargs(Member member) {
+        if (member instanceof Method) { 
+            return ((Method) member).isVarArgs();
+        }
+        if (member instanceof Constructor) {
+            return ((Constructor) member).isVarArgs();
+        }
+        throw new BugException();
+    }
+
+    /**
+     * Returns a more streamlined method or constructor description than {@code Member.toString()} does.
+     */
+    public static String toString(Member member) {
+        if (!(member instanceof Method || member instanceof Constructor)) {
+            throw new IllegalArgumentException("\"member\" must be a Method or Constructor");
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        
+        if ((member.getModifiers() & Modifier.STATIC) != 0) {
+            sb.append("static ");
+        }
+        
+        String className = _ClassUtil.getShortClassName(member.getDeclaringClass());
+        if (className != null) {
+            sb.append(className);
+            sb.append('.');
+        }
+        sb.append(member.getName());
+
+        sb.append('(');
+        Class[] paramTypes = _MethodUtil.getParameterTypes(member);
+        for (int i = 0; i < paramTypes.length; i++) {
+            if (i != 0) sb.append(", ");
+            String paramTypeDecl = _ClassUtil.getShortClassName(paramTypes[i]);
+            if (i == paramTypes.length - 1 && paramTypeDecl.endsWith("[]") && _MethodUtil.isVarargs(member)) {
+                sb.append(paramTypeDecl.substring(0, paramTypeDecl.length() - 2));
+                sb.append("...");
+            } else {
+                sb.append(paramTypeDecl);
+            }
+        }
+        sb.append(')');
+        
+        return sb.toString();
+    }
+
+    public static Object[] invocationErrorMessageStart(Member member) {
+        return invocationErrorMessageStart(member, member instanceof Constructor);
+    }
+    
+    private static Object[] invocationErrorMessageStart(Object member, boolean isConstructor) {
+        return new Object[] { "Java ", isConstructor ? "constructor " : "method ", new _DelayedJQuote(member) };
+    }
+
+    public static TemplateModelException newInvocationTemplateModelException(Object object, Member member, Throwable e) {
+        return newInvocationTemplateModelException(
+                object,
+                member,
+                (member.getModifiers() & Modifier.STATIC) != 0,
+                member instanceof Constructor,
+                e);
+    }
+
+    public static TemplateModelException newInvocationTemplateModelException(Object object, CallableMemberDescriptor callableMemberDescriptor, Throwable e) {
+        return newInvocationTemplateModelException(
+                object,
+                new _DelayedConversionToString(callableMemberDescriptor) {
+                    @Override
+                    protected String doConversion(Object callableMemberDescriptor) {
+                        return ((CallableMemberDescriptor) callableMemberDescriptor).getDeclaration();
+                    }
+                },
+                callableMemberDescriptor.isStatic(),
+                callableMemberDescriptor.isConstructor(),
+                e);
+    }
+    
+    private static TemplateModelException newInvocationTemplateModelException(
+            Object parentObject, Object member, boolean isStatic, boolean isConstructor, Throwable e) {
+        while (e instanceof InvocationTargetException) {
+            Throwable cause = ((InvocationTargetException) e).getTargetException();
+            if (cause != null) {
+                e = cause;
+            } else {
+                break;
+            }
+        }
+
+        return new _TemplateModelException(e,
+                invocationErrorMessageStart(member, isConstructor),
+                " threw an exception",
+                isStatic || isConstructor ? "" : new Object[] {
+                    " when invoked on ", parentObject.getClass(), " object ", new _DelayedJQuote(parentObject) 
+                },
+                "; see cause exception in the Java stack trace.");
+    }
+
+}
\ No newline at end of file

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_ModelImplApi.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_ModelImplApi.java b/src/main/java/org/apache/freemarker/core/model/impl/_ModelImplApi.java
deleted file mode 100644
index 20dd1b7..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/_ModelImplApi.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.model.impl;
-
-/**
- * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
- * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can
- * access things inside this package that users shouldn't. 
- */ 
-public class _ModelImplApi {
-
-    private _ModelImplApi() {
-        // Not to be instantiated
-    }
-    
-    /** For unit testing only */
-    public static void DefaultObjectWrapperFactory_clearInstanceCache() {
-        DefaultObjectWrapperBuilder.clearInstanceCache();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java b/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java
index 5f52b10..7c2a035 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java
@@ -19,8 +19,6 @@
 package org.apache.freemarker.core.model.impl;
 
 import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
 
 /**
  * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
@@ -36,9 +34,6 @@ public final class _StaticObjectWrappers {
     public static final DefaultObjectWrapper DEFAULT_OBJECT_WRAPPER
             = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
 
-    public static final BeansWrapper BEANS_WRAPPER
-            = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-    
     public static final SimpleObjectWrapper SIMPLE_OBJECT_WRAPPER
             = new SimpleObjectWrapper(Configuration.VERSION_3_0_0);
     {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/APIModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/APIModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/APIModel.java
deleted file mode 100644
index 3550fe8..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/APIModel.java
+++ /dev/null
@@ -1,45 +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.beans;
-
-/**
- * Exposes the Java API (and properties) of an object.
- * 
- * <p>
- * Notes:
- * <ul>
- * <li>The exposing level is inherited from the {@link BeansWrapper}</li>
- * <li>But methods will always shadow properties and fields with identical name, regardless of {@link BeansWrapper}
- * settings</li>
- * </ul>
- * 
- * @since 2.3.22
- */
-final class APIModel extends BeanModel {
-
-    APIModel(Object object, BeansWrapper wrapper) {
-        super(object, wrapper, false);
-    }
-
-    protected boolean isMethodsShadowItems() {
-        return true;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ArgumentTypes.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ArgumentTypes.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ArgumentTypes.java
deleted file mode 100644
index abac620..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ArgumentTypes.java
+++ /dev/null
@@ -1,647 +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.beans;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util._ClassUtil;
-
-/**
- * The argument types of a method call; usable as cache key.
- */
-final class ArgumentTypes {
-    
-    /**
-     * Conversion difficulty: Lowest; Java Reflection will do it automatically.
-     */
-    private static final int CONVERSION_DIFFICULTY_REFLECTION = 0;
-
-    /**
-     * Conversion difficulty: Medium: Java reflection API won't convert it, FreeMarker has to do it.
-     */
-    private static final int CONVERSION_DIFFICULTY_FREEMARKER = 1;
-    
-    /**
-     * Conversion difficulty: Highest; conversion is not possible.
-     */
-    private static final int CONVERSION_DIFFICULTY_IMPOSSIBLE = 2;
-
-    /**
-     * The types of the arguments; for varags this contains the exploded list (not the array). 
-     */
-    private final Class<?>[] types;
-    
-    /**
-     * @param args The actual arguments. A varargs argument should be present exploded, no as an array.
-     */
-    ArgumentTypes(Object[] args) {
-        int ln = args.length;
-        Class<?>[] typesTmp = new Class[ln];
-        for (int i = 0; i < ln; ++i) {
-            Object arg = args[i];
-            typesTmp[i] = arg == null
-                    ? Null.class
-                    : arg.getClass();
-        }
-        
-        // `typesTmp` is used so the array is only modified before it's stored in the final `types` field (see JSR-133)
-        types = typesTmp;  
-    }
-    
-    @Override
-    public int hashCode() {
-        int hash = 0;
-        for (Class<?> type : types) {
-            hash ^= type.hashCode();
-        }
-        return hash;
-    }
-    
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof ArgumentTypes) {
-            ArgumentTypes cs = (ArgumentTypes) o;
-            if (cs.types.length != types.length) {
-                return false;
-            }
-            for (int i = 0; i < types.length; ++i) {
-                if (cs.types[i] != types[i]) {
-                    return false;
-                }
-            }
-            return true;
-        }
-        return false;
-    }
-    
-    /**
-     * @return Possibly {@link EmptyCallableMemberDescriptor#NO_SUCH_METHOD} or
-     *         {@link EmptyCallableMemberDescriptor#AMBIGUOUS_METHOD}. 
-     */
-    MaybeEmptyCallableMemberDescriptor getMostSpecific(
-            List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) {
-        LinkedList<CallableMemberDescriptor> applicables = getApplicables(memberDescs, varArg);
-        if (applicables.isEmpty()) {
-            return EmptyCallableMemberDescriptor.NO_SUCH_METHOD;
-        }
-        if (applicables.size() == 1) {
-            return applicables.getFirst();
-        }
-        
-        LinkedList<CallableMemberDescriptor> maximals = new LinkedList<>();
-        for (CallableMemberDescriptor applicable : applicables) {
-            boolean lessSpecific = false;
-            for (Iterator<CallableMemberDescriptor> maximalsIter = maximals.iterator(); 
-                maximalsIter.hasNext(); ) {
-                CallableMemberDescriptor maximal = maximalsIter.next();
-                final int cmpRes = compareParameterListPreferability(
-                        applicable.getParamTypes(), maximal.getParamTypes(), varArg); 
-                if (cmpRes > 0) {
-                    maximalsIter.remove();
-                } else if (cmpRes < 0) {
-                    lessSpecific = true;
-                }
-            }
-            if (!lessSpecific) {
-                maximals.addLast(applicable);
-            }
-        }
-        if (maximals.size() > 1) {
-            return EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD;
-        }
-        return maximals.getFirst();
-    }
-
-    /**
-     * Tells if among the parameter list of two methods, which one fits this argument list better.
-     * This method assumes that the parameter lists are applicable to this argument lists; if that's not ensured,
-     * what the result will be is undefined.
-     * 
-     * <p>The decision is made by comparing the preferability of each parameter types of the same position in a loop.
-     * At the end, the parameter list with the more preferred parameters will be the preferred one. If both parameter
-     * lists has the same amount of preferred parameters, the one that has the first (lower index) preferred parameter
-     * is the preferred one. Otherwise the two parameter list are considered to be equal in terms of preferability.
-     * 
-     * <p>If there's no numerical conversion involved, the preferability of two parameter types is decided on how
-     * specific their types are. For example, {@code String} is more specific than {@link Object} (because
-     * {@code Object.class.isAssignableFrom(String.class)}-s), and so {@code String} is preferred. Primitive
-     * types are considered to be more specific than the corresponding boxing class (like {@code boolean} is more
-     * specific than {@code Boolean}, because the former can't store {@code null}). The preferability decision gets
-     * trickier when there's a possibility of numerical conversion from the actual argument type to the type of some of
-     * the parameters. If such conversion is only possible for one of the competing parameter types, that parameter
-     * automatically wins. If it's possible for both, {@link OverloadedNumberUtil#getArgumentConversionPrice} will
-     * be used to calculate the conversion "price", and the parameter type with lowest price wins. There are also
-     * a twist with array-to-list and list-to-array conversions; we try to avoid those, so the parameter where such
-     * conversion isn't needed will always win.
-     * 
-     * @param paramTypes1 The parameter types of one of the competing methods
-     * @param paramTypes2 The parameter types of the other competing method
-     * @param varArg Whether these competing methods are varargs methods. 
-     * @return More than 0 if the first parameter list is preferred, less then 0 if the other is preferred,
-     *        0 if there's no decision 
-     */
-    int compareParameterListPreferability(Class<?>[] paramTypes1, Class<?>[] paramTypes2, boolean varArg) {
-        final int argTypesLen = types.length; 
-        final int paramTypes1Len = paramTypes1.length;
-        final int paramTypes2Len = paramTypes2.length;
-        //assert varArg || paramTypes1Len == paramTypes2Len;
-        
-        int paramList1WeakWinCnt = 0;
-        int paramList2WeakWinCnt = 0;
-        int paramList1WinCnt = 0;
-        int paramList2WinCnt = 0;
-        int paramList1StrongWinCnt = 0;
-        int paramList2StrongWinCnt = 0;
-        int paramList1VeryStrongWinCnt = 0;
-        int paramList2VeryStrongWinCnt = 0;
-        int firstWinerParamList = 0;
-        for (int i = 0; i < argTypesLen; i++) {
-            final Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, i, varArg);
-            final Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, i, varArg);
-            
-            final int winerParam;  // 1 => paramType1; -1 => paramType2; 0 => draw
-            if (paramType1 == paramType2) {
-                winerParam = 0;
-            } else {
-                final Class<?> argType = types[i];
-                final boolean argIsNum = Number.class.isAssignableFrom(argType);
-                
-                final int numConvPrice1;
-                if (argIsNum && _ClassUtil.isNumerical(paramType1)) {
-                    final Class<?> nonPrimParamType1 = paramType1.isPrimitive()
-                            ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1;
-                    numConvPrice1 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType1);
-                } else {
-                    numConvPrice1 = Integer.MAX_VALUE;
-                }
-                // numConvPrice1 is Integer.MAX_VALUE if either:
-                // - argType and paramType1 aren't both numerical
-                // - FM doesn't know some of the numerical types, or the conversion between them is not allowed    
-                
-                final int numConvPrice2;
-                if (argIsNum && _ClassUtil.isNumerical(paramType2)) {
-                    final Class<?> nonPrimParamType2 = paramType2.isPrimitive()
-                            ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2;
-                    numConvPrice2 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType2);
-                } else {
-                    numConvPrice2 = Integer.MAX_VALUE;
-                }
-                
-                if (numConvPrice1 == Integer.MAX_VALUE) {
-                    if (numConvPrice2 == Integer.MAX_VALUE) {  // No numerical conversions anywhere
-                        // List to array conversions (unwrapping sometimes makes a List instead of an array)
-                        if (List.class.isAssignableFrom(argType)
-                                && (paramType1.isArray() || paramType2.isArray())) {
-                            if (paramType1.isArray()) {
-                                if (paramType2.isArray()) {  // both paramType1 and paramType2 are arrays
-                                    int r = compareParameterListPreferability_cmpTypeSpecificty(
-                                            paramType1.getComponentType(), paramType2.getComponentType());
-                                    // Because we don't know if the List items are instances of the component
-                                    // type or not, we prefer the safer choice, which is the more generic array:
-                                    if (r > 0) {
-                                        winerParam = 2;
-                                        paramList2StrongWinCnt++;
-                                    } else if (r < 0) {
-                                        winerParam = 1;
-                                        paramList1StrongWinCnt++;
-                                    } else {
-                                        winerParam = 0;
-                                    }
-                                } else {  // paramType1 is array, paramType2 isn't
-                                    // Avoid List to array conversion if the other way makes any sense:
-                                    if (Collection.class.isAssignableFrom(paramType2)) {
-                                        winerParam = 2;
-                                        paramList2StrongWinCnt++;
-                                    } else {
-                                        winerParam = 1;
-                                        paramList1WeakWinCnt++;
-                                    }
-                                }
-                            } else {  // paramType2 is array, paramType1 isn't
-                                // Avoid List to array conversion if the other way makes any sense:
-                                if (Collection.class.isAssignableFrom(paramType1)) {
-                                    winerParam = 1;
-                                    paramList1StrongWinCnt++;
-                                } else {
-                                    winerParam = 2;
-                                    paramList2WeakWinCnt++;
-                                }
-                            }
-                        } else if (argType.isArray()
-                                && (List.class.isAssignableFrom(paramType1)
-                                        || List.class.isAssignableFrom(paramType2))) {
-                            // Array to List conversions (unwrapping sometimes makes an array instead of a List)
-                            if (List.class.isAssignableFrom(paramType1)) {
-                                if (List.class.isAssignableFrom(paramType2)) {
-                                    // Both paramType1 and paramType2 extends List
-                                    winerParam = 0;
-                                } else {
-                                    // Only paramType1 extends List
-                                    winerParam = 2;
-                                    paramList2VeryStrongWinCnt++;
-                                }
-                            } else {
-                                // Only paramType2 extends List
-                                winerParam = 1;
-                                paramList1VeryStrongWinCnt++;
-                            }
-                        } else {  // No list to/from array conversion
-                            final int r = compareParameterListPreferability_cmpTypeSpecificty(
-                                    paramType1, paramType2);
-                            if (r > 0) {
-                                winerParam = 1;
-                                if (r > 1) {
-                                    paramList1WinCnt++;
-                                } else {
-                                    paramList1WeakWinCnt++;
-                                }
-                            } else if (r < 0) {
-                                winerParam = -1;
-                                if (r < -1) {
-                                    paramList2WinCnt++;
-                                } else {
-                                    paramList2WeakWinCnt++;
-                                }
-                            } else {
-                                winerParam = 0;
-                            }
-                        }
-                    } else {  // No num. conv. of param1, num. conv. of param2
-                        winerParam = -1;
-                        paramList2WinCnt++;
-                    }
-                } else if (numConvPrice2 == Integer.MAX_VALUE) {  // Num. conv. of param1, not of param2
-                    winerParam = 1;
-                    paramList1WinCnt++;
-                } else {  // Num. conv. of both param1 and param2
-                    if (numConvPrice1 != numConvPrice2) {
-                        if (numConvPrice1 < numConvPrice2) {
-                            winerParam = 1;
-                            if (numConvPrice1 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE
-                                    && numConvPrice2 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) {
-                                paramList1StrongWinCnt++;
-                            } else {
-                                paramList1WinCnt++;
-                            }
-                        } else {
-                            winerParam = -1;
-                            if (numConvPrice2 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE
-                                    && numConvPrice1 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) {
-                                paramList2StrongWinCnt++;
-                            } else {
-                                paramList2WinCnt++;
-                            }
-                        }
-                    } else {
-                        winerParam = (paramType1.isPrimitive() ? 1 : 0) - (paramType2.isPrimitive() ? 1 : 0);
-                        if (winerParam == 1) paramList1WeakWinCnt++;
-                        else if (winerParam == -1) paramList2WeakWinCnt++;
-                    }
-                }
-            }  // when paramType1 != paramType2
-            
-            if (firstWinerParamList == 0 && winerParam != 0) {
-                firstWinerParamList = winerParam; 
-            }
-        }  // for each parameter types
-        
-        if (paramList1VeryStrongWinCnt != paramList2VeryStrongWinCnt) {
-            return paramList1VeryStrongWinCnt - paramList2VeryStrongWinCnt;
-        } else if (paramList1StrongWinCnt != paramList2StrongWinCnt) {
-            return paramList1StrongWinCnt - paramList2StrongWinCnt;
-        } else if (paramList1WinCnt != paramList2WinCnt) {
-            return paramList1WinCnt - paramList2WinCnt;
-        } else if (paramList1WeakWinCnt != paramList2WeakWinCnt) {
-            return paramList1WeakWinCnt - paramList2WeakWinCnt;
-        } else if (firstWinerParamList != 0) {  // paramList1WinCnt == paramList2WinCnt
-            return firstWinerParamList;
-        } else { // still undecided
-            if (varArg) {
-                if (paramTypes1Len == paramTypes2Len) {
-                    // If we had a 0-length varargs array in both methods, we also compare the types at the
-                    // index of the varargs parameter, like if we had a single varargs argument. However, this
-                    // time we don't have an argument type, so we can only decide based on type specificity:
-                    if (argTypesLen == paramTypes1Len - 1) {
-                        Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, argTypesLen, true);
-                        Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, argTypesLen, true);
-                        if (_ClassUtil.isNumerical(paramType1) && _ClassUtil.isNumerical(paramType2)) {
-                            int r = OverloadedNumberUtil.compareNumberTypeSpecificity(paramType1, paramType2);
-                            if (r != 0) return r;
-                            // falls through
-                        }
-                        return compareParameterListPreferability_cmpTypeSpecificty(paramType1, paramType2);
-                    } else {
-                        return 0;
-                    }
-                } else {
-                    // The method with more oms parameters wins:
-                    return paramTypes1Len - paramTypes2Len;
-                }
-            } else {
-                return 0;
-            }
-        }
-    }
-    
-    /**
-     * Trivial comparison of type specificities; unaware of numerical conversions. 
-     * 
-     * @return Less-than-0, 0, or more-than-0 depending on which side is more specific. The absolute value is 1 if
-     *     the difference is only in primitive VS non-primitive, more otherwise.
-     */
-    private int compareParameterListPreferability_cmpTypeSpecificty(
-            final Class<?> paramType1, final Class<?> paramType2) {
-        // The more specific (smaller) type wins.
-        
-        final Class<?> nonPrimParamType1 = paramType1.isPrimitive()
-                ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1;
-        final Class<?> nonPrimParamType2 = paramType2.isPrimitive()
-                ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2;
-                
-        if (nonPrimParamType1 == nonPrimParamType2) {
-            if (nonPrimParamType1 != paramType1) {
-                if (nonPrimParamType2 != paramType2) {
-                    return 0;  // identical prim. types; shouldn't ever be reached
-                } else {
-                    return 1;  // param1 is prim., param2 is non prim.
-                }
-            } else if (nonPrimParamType2 != paramType2) {
-                return -1;  // param1 is non-prim., param2 is prim.
-            } else {
-                return 0;  // identical non-prim. types
-            }
-        } else if (nonPrimParamType2.isAssignableFrom(nonPrimParamType1)) {
-            return 2;
-        } else if (nonPrimParamType1.isAssignableFrom(nonPrimParamType2)) {
-            return -2;
-        } if (nonPrimParamType1 == Character.class && nonPrimParamType2.isAssignableFrom(String.class)) {
-            return 2;  // A character is a 1 long string in FTL, so we pretend that it's a String subtype.
-        } if (nonPrimParamType2 == Character.class && nonPrimParamType1.isAssignableFrom(String.class)) {
-            return -2;
-        } else {
-            return 0;  // unrelated types
-        }
-    }
-
-    private static Class<?> getParamType(Class<?>[] paramTypes, int paramTypesLen, int i, boolean varArg) {
-        return varArg && i >= paramTypesLen - 1
-                ? paramTypes[paramTypesLen - 1].getComponentType()
-                : paramTypes[i];
-    }
-    
-    /**
-     * Returns all methods that are applicable to actual
-     * parameter types represented by this ArgumentTypes object.
-     */
-    LinkedList<CallableMemberDescriptor> getApplicables(
-            List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) {
-        LinkedList<CallableMemberDescriptor> applicables = new LinkedList<>();
-        for (ReflectionCallableMemberDescriptor memberDesc : memberDescs) {
-            int difficulty = isApplicable(memberDesc, varArg);
-            if (difficulty != CONVERSION_DIFFICULTY_IMPOSSIBLE) {
-                if (difficulty == CONVERSION_DIFFICULTY_REFLECTION) {
-                    applicables.add(memberDesc);
-                } else if (difficulty == CONVERSION_DIFFICULTY_FREEMARKER) {
-                    applicables.add(new SpecialConversionCallableMemberDescriptor(memberDesc));
-                } else {
-                    throw new BugException();
-                }
-            }
-        }
-        return applicables;
-    }
-    
-    /**
-     * Returns if the supplied method is applicable to actual
-     * parameter types represented by this ArgumentTypes object, also tells
-     * how difficult that conversion is.
-     * 
-     * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants.
-     */
-    private int isApplicable(ReflectionCallableMemberDescriptor memberDesc, boolean varArg) {
-        final Class<?>[] paramTypes = memberDesc.getParamTypes(); 
-        final int cl = types.length;
-        final int fl = paramTypes.length - (varArg ? 1 : 0);
-        if (varArg) {
-            if (cl < fl) {
-                return CONVERSION_DIFFICULTY_IMPOSSIBLE;
-            }
-        } else {
-            if (cl != fl) {
-                return CONVERSION_DIFFICULTY_IMPOSSIBLE;
-            }
-        }
-        
-        int maxDifficulty = 0;
-        for (int i = 0; i < fl; ++i) {
-            int difficulty = isMethodInvocationConvertible(paramTypes[i], types[i]);
-            if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) {
-                return CONVERSION_DIFFICULTY_IMPOSSIBLE;
-            }
-            if (maxDifficulty < difficulty) {
-                maxDifficulty = difficulty;
-            }
-        }
-        if (varArg) {
-            Class<?> varArgParamType = paramTypes[fl].getComponentType();
-            for (int i = fl; i < cl; ++i) {
-                int difficulty = isMethodInvocationConvertible(varArgParamType, types[i]); 
-                if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) {
-                    return CONVERSION_DIFFICULTY_IMPOSSIBLE;
-                }
-                if (maxDifficulty < difficulty) {
-                    maxDifficulty = difficulty;
-                }
-            }
-        }
-        return maxDifficulty;
-    }
-
-    /**
-     * Determines whether a type is convertible to another type via 
-     * method invocation conversion, and if so, what kind of conversion is needed.
-     * It treates the object type counterpart of primitive types as if they were the primitive types
-     * (that is, a Boolean actual parameter type matches boolean primitive formal type). This behavior
-     * is because this method is used to determine applicable methods for 
-     * an actual parameter list, and primitive types are represented by 
-     * their object duals in reflective method calls.
-     * @param formal the parameter type to which the actual 
-     * parameter type should be convertible; possibly a primitive type
-     * @param actual the argument type; not a primitive type, maybe {@link Null}.
-     * 
-     * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants.
-     */
-    private int isMethodInvocationConvertible(final Class<?> formal, final Class<?> actual) {
-        // Check for identity or widening reference conversion
-        if (formal.isAssignableFrom(actual) && actual != CharacterOrString.class) {
-            return CONVERSION_DIFFICULTY_REFLECTION;
-        } else {
-            final Class<?> formalNP;
-            if (formal.isPrimitive()) {
-                if (actual == Null.class) {
-                    return CONVERSION_DIFFICULTY_IMPOSSIBLE;
-                }
-                
-                formalNP = _ClassUtil.primitiveClassToBoxingClass(formal);
-                if (actual == formalNP) {
-                    // Character and char, etc.
-                    return CONVERSION_DIFFICULTY_REFLECTION;
-                }
-            } else {  // formal is non-primitive
-                if (actual == Null.class) {
-                    return CONVERSION_DIFFICULTY_REFLECTION;
-                }
-                
-                formalNP = formal;
-            }
-            if (Number.class.isAssignableFrom(actual) && Number.class.isAssignableFrom(formalNP)) {
-                return OverloadedNumberUtil.getArgumentConversionPrice(actual, formalNP) == Integer.MAX_VALUE
-                        ? CONVERSION_DIFFICULTY_IMPOSSIBLE : CONVERSION_DIFFICULTY_REFLECTION;
-            } else if (formal.isArray()) {
-                // BeansWrapper method/constructor calls convert from List to array automatically
-                return List.class.isAssignableFrom(actual)
-                        ? CONVERSION_DIFFICULTY_FREEMARKER : CONVERSION_DIFFICULTY_IMPOSSIBLE;
-            } else if (actual.isArray() && formal.isAssignableFrom(List.class)) {
-                // BeansWrapper method/constructor calls convert from array to List automatically
-                return CONVERSION_DIFFICULTY_FREEMARKER;
-            } else if (actual == CharacterOrString.class
-                    && (formal.isAssignableFrom(String.class)
-                            || formal.isAssignableFrom(Character.class) || formal == char.class)) {
-                return CONVERSION_DIFFICULTY_FREEMARKER;
-            } else {
-                return CONVERSION_DIFFICULTY_IMPOSSIBLE;
-            }
-        }
-    }
-    
-    /**
-     * Symbolizes the class of null (it's missing from Java).
-     */
-    private static class Null {
-        
-        // Can't be instantiated
-        private Null() { }
-        
-    }
-    
-    /**
-     * Used instead of {@link ReflectionCallableMemberDescriptor} when the method is only applicable
-     * ({@link #isApplicable}) with conversion that Java reflection won't do. It delegates to a
-     * {@link ReflectionCallableMemberDescriptor}, but it adds the necessary conversions to the invocation methods. 
-     */
-    private static final class SpecialConversionCallableMemberDescriptor extends CallableMemberDescriptor {
-        
-        private final ReflectionCallableMemberDescriptor callableMemberDesc;
-
-        SpecialConversionCallableMemberDescriptor(ReflectionCallableMemberDescriptor callableMemberDesc) {
-            this.callableMemberDesc = callableMemberDesc;
-        }
-
-        @Override
-        TemplateModel invokeMethod(BeansWrapper bw, Object obj, Object[] args) throws TemplateModelException,
-                InvocationTargetException, IllegalAccessException {
-            convertArgsToReflectionCompatible(bw, args);
-            return callableMemberDesc.invokeMethod(bw, obj, args);
-        }
-
-        @Override
-        Object invokeConstructor(BeansWrapper bw, Object[] args) throws IllegalArgumentException,
-                InstantiationException, IllegalAccessException, InvocationTargetException, TemplateModelException {
-            convertArgsToReflectionCompatible(bw, args);
-            return callableMemberDesc.invokeConstructor(bw, args);
-        }
-
-        @Override
-        String getDeclaration() {
-            return callableMemberDesc.getDeclaration();
-        }
-
-        @Override
-        boolean isConstructor() {
-            return callableMemberDesc.isConstructor();
-        }
-
-        @Override
-        boolean isStatic() {
-            return callableMemberDesc.isStatic();
-        }
-
-        @Override
-        boolean isVarargs() {
-            return callableMemberDesc.isVarargs();
-        }
-
-        @Override
-        Class<?>[] getParamTypes() {
-            return callableMemberDesc.getParamTypes();
-        }
-        
-        @Override
-        String getName() {
-            return callableMemberDesc.getName();
-        }
-
-        private void convertArgsToReflectionCompatible(BeansWrapper bw, Object[] args) throws TemplateModelException {
-            Class<?>[] paramTypes = callableMemberDesc.getParamTypes();
-            int ln = paramTypes.length;
-            for (int i = 0; i < ln; i++) {
-                Class<?> paramType = paramTypes[i];
-                final Object arg = args[i];
-                if (arg == null) continue;
-                
-                // Handle conversion between List and array types, in both directions. Java reflection won't do such
-                // conversion, so we have to.
-                // Most reflection-incompatible conversions were already addressed by the unwrapping. The reason
-                // this one isn't is that for overloaded methods the hint of a given parameter position is often vague,
-                // so we may end up with a List even if some parameter types at that position are arrays (remember, we
-                // have to chose one unwrapping target type, despite that we have many possible overloaded methods), or
-                // the other way around (that happens when AdapterTemplateMoldel returns an array).
-                // Later, the overloaded method selection will assume that a List argument is applicable to an array
-                // parameter, and that an array argument is applicable to a List parameter, so we end up with this
-                // situation.
-                if (paramType.isArray() && arg instanceof List) {
-                   args[i] = bw.listToArray((List<?>) arg, paramType, null);
-                }
-                if (arg.getClass().isArray() && paramType.isAssignableFrom(List.class)) {
-                    args[i] = bw.arrayToList(arg);
-                }
-                
-                // Handle the conversion from CharacterOrString to Character or String:
-                if (arg instanceof CharacterOrString) {
-                    if (paramType == Character.class || paramType == char.class
-                            || (!paramType.isAssignableFrom(String.class)
-                                    && paramType.isAssignableFrom(Character.class))) {
-                        args[i] = Character.valueOf(((CharacterOrString) arg).getAsChar());
-                    } else {
-                        args[i] = ((CharacterOrString) arg).getAsString();
-                    }
-                }
-            }
-        }
-
-    }
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ArrayModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ArrayModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ArrayModel.java
deleted file mode 100644
index 8be341c..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ArrayModel.java
+++ /dev/null
@@ -1,126 +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.beans;
-
-import java.lang.reflect.Array;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-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.TemplateSequenceModel;
-
-/**
- * <p>A class that will wrap an arbitrary array into {@link TemplateCollectionModel}
- * and {@link TemplateSequenceModel} interfaces. It supports element retrieval through the <tt>array[index]</tt>
- * syntax and can be iterated as a list.
- */
-public class ArrayModel
-extends
-    BeanModel
-implements
-    TemplateCollectionModel,
-    TemplateSequenceModel {
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new ArrayModel(object, (BeansWrapper) wrapper);
-            }
-        };
-        
-    // Cached length of the array
-    private final int length;
-
-    /**
-     * Creates a new model that wraps the specified array object.
-     * @param array the array object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     * @throws IllegalArgumentException if the passed object is not a Java array.
-     */
-    public ArrayModel(Object array, BeansWrapper wrapper) {
-        super(array, wrapper);
-        Class clazz = array.getClass();
-        if (!clazz.isArray())
-            throw new IllegalArgumentException("Object is not an array, it's " + array.getClass().getName());
-        length = Array.getLength(array);
-    }
-
-
-    @Override
-    public TemplateModelIterator iterator() {
-        return new Iterator();
-    }
-
-    @Override
-    public TemplateModel get(int index)
-    throws TemplateModelException {
-        try {
-            return wrap(Array.get(object, index));
-        } catch (IndexOutOfBoundsException e) {
-            return null; 
-//            throw new TemplateModelException("Index out of bounds: " + index);
-        }
-    }
-
-    private class Iterator
-    implements 
-        TemplateSequenceModel,
-        TemplateModelIterator {
-        private int position = 0;
-
-        @Override
-        public boolean hasNext() {
-            return position < length;
-        }
-
-        @Override
-        public TemplateModel get(int index)
-        throws TemplateModelException {
-            return ArrayModel.this.get(index);
-        }
-
-        @Override
-        public TemplateModel next()
-        throws TemplateModelException {
-            return position < length ? get(position++) : null;
-        }
-
-        @Override
-        public int size() {
-            return ArrayModel.this.size();
-        }
-    }
-
-    @Override
-    public int size() {
-        return length;
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return length == 0;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeanModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeanModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeanModel.java
deleted file mode 100644
index 5c2db2b..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeanModel.java
+++ /dev/null
@@ -1,365 +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.beans;
-
-import java.beans.IndexedPropertyDescriptor;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.freemarker.core._CoreLogs;
-import org.apache.freemarker.core._DelayedFTLTypeDescription;
-import org.apache.freemarker.core._DelayedJQuote;
-import org.apache.freemarker.core._TemplateModelException;
-import org.apache.freemarker.core.model.AdapterTemplateModel;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-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.TemplateModelIterator;
-import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.WrapperTemplateModel;
-import org.apache.freemarker.core.model.impl.CollectionAndSequence;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.apache.freemarker.core.model.impl.SimpleSequence;
-import org.apache.freemarker.core.util._StringUtil;
-import org.slf4j.Logger;
-
-/**
- * A class that will wrap an arbitrary object into {@link org.apache.freemarker.core.model.TemplateHashModel}
- * interface allowing calls to arbitrary property getters and invocation of
- * accessible methods on the object from a template using the
- * <tt>object.foo</tt> to access properties and <tt>object.bar(arg1, arg2)</tt> to
- * invoke methods on it. You can also use the <tt>object.foo[index]</tt> syntax to
- * access indexed properties. It uses Beans {@link java.beans.Introspector}
- * to dynamically discover the properties and methods. 
- */
-
-public class BeanModel
-implements
-    TemplateHashModelEx, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport {
-    
-    private static final Logger LOG = _CoreLogs.BEANS_WRAPPER;
-    
-    protected final Object object;
-    protected final BeansWrapper wrapper;
-    
-    // We use this to represent an unknown value as opposed to known value of null (JR)
-    static final TemplateModel UNKNOWN = new SimpleScalar("UNKNOWN");
-    
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new BeanModel(object, (BeansWrapper) wrapper);
-            }
-        };
-
-    // I've tried to use a volatile ConcurrentHashMap field instead of HashMap + synchronized(this), but oddly it was
-    // a bit slower, at least on Java 8 u66. 
-    private HashMap<Object, TemplateModel> memberCache;
-
-    /**
-     * Creates a new model that wraps the specified object. Note that there are
-     * specialized subclasses of this class for wrapping arrays, collections,
-     * enumeration, iterators, and maps. Note also that the superclass can be
-     * used to wrap String objects if only scalar functionality is needed. You
-     * can also choose to delegate the choice over which model class is used for
-     * wrapping to {@link BeansWrapper#wrap(Object)}.
-     * @param object the object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     */
-    public BeanModel(Object object, BeansWrapper wrapper) {
-        // [2.4]: All models were introspected here, then the results was discareded, and get() will just do the
-        // introspection again. So is this necessary? (The inrospectNow parameter was added in 2.3.21 to allow
-        // lazy-introspecting BeansWrapper.trueModel|falseModel.)
-        this(object, wrapper, true);
-    }
-
-    /** @since 2.3.21 */
-    BeanModel(Object object, BeansWrapper wrapper, boolean inrospectNow) {
-        this.object = object;
-        this.wrapper = wrapper;
-        if (inrospectNow && object != null) {
-            // [2.4]: Could this be removed?
-            wrapper.getClassIntrospector().get(object.getClass());
-        }
-    }
-    
-    /**
-     * Uses Beans introspection to locate a property or method with name
-     * matching the key name. If a method or property is found, it's wrapped
-     * into {@link org.apache.freemarker.core.model.TemplateMethodModelEx} (for a method or
-     * indexed property), or evaluated on-the-fly and the return value wrapped
-     * into appropriate model (for a simple property) Models for various
-     * properties and methods are cached on a per-class basis, so the costly
-     * introspection is performed only once per property or method of a class.
-     * (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-
-     * 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
-     * environment that does this kind of things--servlet containers do it when
-     * they reload a web application) and flushes the cache. If no method or
-     * property matching the key is found, the framework will try to invoke
-     * methods with signature
-     * <tt>non-void-return-type get(java.lang.String)</tt>,
-     * then <tt>non-void-return-type get(java.lang.Object)</tt>, or 
-     * alternatively (if the wrapped object is a resource bundle) 
-     * <tt>Object getObject(java.lang.String)</tt>.
-     * @throws TemplateModelException if there was no property nor method nor
-     * a generic <tt>get</tt> method to invoke.
-     */
-    @Override
-    public TemplateModel get(String key)
-        throws TemplateModelException {
-        Class<?> clazz = object.getClass();
-        Map<Object, Object> classInfo = wrapper.getClassIntrospector().get(clazz);
-        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);
-                }
-            } 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;
-                    }
-                }
-            }
-            if (retval == UNKNOWN) {
-                if (wrapper.isStrict()) {
-                    throw new InvalidPropertyException("No such bean property: " + key);
-                } else {
-                    logNoSuchKey(key, classInfo);
-                }
-                retval = wrapper.wrap(null);
-            }
-            return retval;
-        } catch (TemplateModelException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new _TemplateModelException(e,
-                    "An error has occurred when reading existing sub-variable ", new _DelayedJQuote(key),
-                    "; see cause exception! The type of the containing value was: ",
-                    new _DelayedFTLTypeDescription(this)
-            );
-        }
-    }
-
-    private void logNoSuchKey(String key, Map<?, ?> keyMap) {
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Key " + _StringUtil.jQuoteNoXSS(key) + " was not found on instance of " + 
-                object.getClass().getName() + ". Introspection information for " +
-                "the class is: " + keyMap);
-        }
-    }
-    
-    /**
-     * Whether the model has a plain get(String) or get(Object) method
-     */
-    
-    protected boolean hasPlainGetMethod() {
-        return wrapper.getClassIntrospector().get(object.getClass()).get(ClassIntrospector.GENERIC_GET_KEY) != null;
-    }
-    
-    private TemplateModel invokeThroughDescriptor(Object desc, Map<Object, Object> classInfo)
-            throws IllegalAccessException, InvocationTargetException, TemplateModelException {
-        // See if this particular instance has a cached implementation for the requested feature descriptor
-        TemplateModel cachedModel;
-        synchronized (this) {
-            cachedModel = memberCache != null ? memberCache.get(desc) : null;
-        }
-
-        if (cachedModel != null) {
-            return cachedModel;
-        }
-
-        TemplateModel resultModel = UNKNOWN;
-        if (desc instanceof IndexedPropertyDescriptor) {
-            Method readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod(); 
-            resultModel = cachedModel = 
-                new SimpleMethodModel(object, readMethod, 
-                        ClassIntrospector.getArgTypes(classInfo, readMethod), wrapper);
-        } else if (desc instanceof PropertyDescriptor) {
-            PropertyDescriptor pd = (PropertyDescriptor) desc;
-            resultModel = wrapper.invokeMethod(object, pd.getReadMethod(), null);
-            // cachedModel remains null, as we don't cache these
-        } else if (desc instanceof Field) {
-            resultModel = wrapper.wrap(((Field) desc).get(object));
-            // cachedModel remains null, as we don't cache these
-        } else if (desc instanceof Method) {
-            Method method = (Method) desc;
-            resultModel = cachedModel = new SimpleMethodModel(
-                    object, method, ClassIntrospector.getArgTypes(classInfo, method), wrapper);
-        } else if (desc instanceof OverloadedMethods) {
-            resultModel = cachedModel = new OverloadedMethodsModel(
-                    object, (OverloadedMethods) desc, wrapper);
-        }
-        
-        // If new cachedModel was created, cache it
-        if (cachedModel != null) {
-            synchronized (this) {
-                if (memberCache == null) {
-                    memberCache = new HashMap<>();
-                }
-                memberCache.put(desc, cachedModel);
-            }
-        }
-        return resultModel;
-    }
-    
-    void clearMemberCache() {
-        synchronized (this) {
-            memberCache = null;
-        }
-    }
-
-    protected TemplateModel invokeGenericGet(Map/*<Object, Object>*/ classInfo, Class<?> clazz, String key)
-            throws IllegalAccessException, InvocationTargetException,
-        TemplateModelException {
-        Method genericGet = (Method) classInfo.get(ClassIntrospector.GENERIC_GET_KEY);
-        if (genericGet == null) {
-            return UNKNOWN;
-        }
-
-        return wrapper.invokeMethod(object, genericGet, new Object[] { key });
-    }
-
-    protected TemplateModel wrap(Object obj)
-    throws TemplateModelException {
-        return wrapper.getOuterIdentity().wrap(obj);
-    }
-    
-    protected Object unwrap(TemplateModel model)
-    throws TemplateModelException {
-        return wrapper.unwrap(model);
-    }
-
-    /**
-     * Tells whether the model is considered to be empty.
-     * It is empty if the wrapped object is a 0 length {@link String}, or an empty {@link Collection} or and empty
-     * {@link Map}, or an {@link Iterator} that has no more items, or a {@link Boolean#FALSE}, or {@code null}. 
-     */
-    @Override
-    public boolean isEmpty() {
-        if (object instanceof String) {
-            return ((String) object).length() == 0;
-        }
-        if (object instanceof Collection) {
-            return ((Collection<?>) object).isEmpty();
-        }
-        if (object instanceof Iterator) {
-            return !((Iterator<?>) object).hasNext();
-        }
-        if (object instanceof Map) {
-            return ((Map<?,?>) object).isEmpty();
-        }
-        // [FM3] Why's FALSE empty? 
-        return object == null || Boolean.FALSE.equals(object);
-    }
-    
-    /**
-     * Returns the same as {@link #getWrappedObject()}; to ensure that, this method will be final starting from 2.4.
-     * This behavior of {@link BeanModel} is assumed by some FreeMarker code. 
-     */
-    @Override
-    public Object getAdaptedObject(Class<?> hint) {
-        return object;  // return getWrappedObject(); starting from 2.4
-    }
-
-    @Override
-    public Object getWrappedObject() {
-        return object;
-    }
-    
-    @Override
-    public int size() {
-        return wrapper.getClassIntrospector().keyCount(object.getClass());
-    }
-
-    @Override
-    public TemplateCollectionModel keys() {
-        return new CollectionAndSequence(new SimpleSequence(keySet(), wrapper));
-    }
-
-    @Override
-    public TemplateCollectionModel values() throws TemplateModelException {
-        List<Object> values = new ArrayList<>(size());
-        TemplateModelIterator it = keys().iterator();
-        while (it.hasNext()) {
-            String key = ((TemplateScalarModel) it.next()).getAsString();
-            values.add(get(key));
-        }
-        return new CollectionAndSequence(new SimpleSequence(values, wrapper));
-    }
-    
-    @Override
-    public String toString() {
-        return object.toString();
-    }
-
-    /**
-     * Helper method to support TemplateHashModelEx. Returns the Set of
-     * Strings which are available via the TemplateHashModel
-     * interface. Subclasses that override <tt>invokeGenericGet</tt> to
-     * provide additional hash keys should also override this method.
-     */
-    protected Set/*<Object>*/ keySet() {
-        return wrapper.getClassIntrospector().keySet(object.getClass());
-    }
-
-    @Override
-    public TemplateModel getAPI() throws TemplateModelException {
-        return wrapper.wrapAsAPI(object);
-    }
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansModelCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansModelCache.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansModelCache.java
deleted file mode 100644
index 52db93d..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansModelCache.java
+++ /dev/null
@@ -1,73 +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.beans;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.freemarker.core.model.TemplateModel;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-public class BeansModelCache extends ModelCache {
-    private final Map classToFactory = new ConcurrentHashMap();
-    private final Set mappedClassNames = new HashSet();
-
-    private final BeansWrapper wrapper;
-    
-    BeansModelCache(BeansWrapper wrapper) {
-        this.wrapper = wrapper;
-    }
-    
-    @Override
-    protected boolean isCacheable(Object object) {
-        return object.getClass() != Boolean.class; 
-    }
-    
-    @Override
-    @SuppressFBWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER", justification="Locks for factory creation only")
-    protected TemplateModel create(Object object) {
-        Class clazz = object.getClass();
-        
-        ModelFactory factory = (ModelFactory) classToFactory.get(clazz);
-        
-        if (factory == null) {
-            // Synchronized so that we won't unnecessarily create the same factory for multiple times in parallel.
-            synchronized (classToFactory) {
-                factory = (ModelFactory) classToFactory.get(clazz);
-                if (factory == null) {
-                    String className = clazz.getName();
-                    // clear mappings when class reloading is detected
-                    if (!mappedClassNames.add(className)) {
-                        classToFactory.clear();
-                        mappedClassNames.clear();
-                        mappedClassNames.add(className);
-                    }
-                    factory = wrapper.getModelFactory(clazz);
-                    classToFactory.put(clazz, factory);
-                }
-            }
-        }
-        
-        return factory.create(object, wrapper);
-    }
-}


[16/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java
new file mode 100644
index 0000000..204ff1f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java
@@ -0,0 +1,82 @@
+/*
+ * 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.util.Collections;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+
+/**
+ * Wraps a set of same-name overloaded methods behind {@link org.apache.freemarker.core.model.TemplateMethodModel} interface,
+ * like if it was a single method, chooses among them behind the scenes on call-time based on the argument values.
+ */
+public class OverloadedMethodsModel
+implements
+	TemplateMethodModelEx,
+	TemplateSequenceModel {
+    private final Object object;
+    private final OverloadedMethods overloadedMethods;
+    private final DefaultObjectWrapper wrapper;
+    
+    OverloadedMethodsModel(Object object, OverloadedMethods overloadedMethods, DefaultObjectWrapper wrapper) {
+        this.object = object;
+        this.overloadedMethods = overloadedMethods;
+        this.wrapper = wrapper;
+    }
+
+    /**
+     * Invokes the method, passing it the arguments from the list. The actual
+     * method to call from several overloaded methods will be chosen based
+     * on the classes of the arguments.
+     * @throws TemplateModelException if the method cannot be chosen
+     * unambiguously.
+     */
+    @Override
+    public Object exec(List arguments)
+    throws TemplateModelException {
+        MemberAndArguments maa = overloadedMethods.getMemberAndArguments(arguments, wrapper);
+        try {
+            return maa.invokeMethod(wrapper, object);
+        } catch (Exception e) {
+            if (e instanceof TemplateModelException) throw (TemplateModelException) e;
+            
+            throw _MethodUtil.newInvocationTemplateModelException(
+                    object,
+                    maa.getCallableMemberDescriptor(),
+                    e);
+        }
+    }
+
+    @Override
+    public TemplateModel get(int index) throws TemplateModelException {
+        return (TemplateModel) exec(Collections.singletonList(
+                new SimpleNumber(Integer.valueOf(index))));
+    }
+
+    @Override
+    public int size() throws TemplateModelException {
+        throw new TemplateModelException("?size is unsupported for " + getClass().getName());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..0c10a6b
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
@@ -0,0 +1,402 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Encapsulates the rules and data structures (including cache) for choosing of the best matching callable member for
+ * a parameter list, from a given set of callable members. There are two subclasses of this, one for non-varags methods,
+ * and one for varargs methods.
+ */
+abstract class OverloadedMethodsSubset {
+    
+    /** 
+     * Used for an optimization trick to substitute an array of whatever size that contains only 0-s. Since this array
+     * is 0 long, this means that the code that reads the int[] always have to check if the int[] has this value, and
+     * then it has to act like if was all 0-s.  
+     */
+    static final int[] ALL_ZEROS_ARRAY = new int[0];
+
+    private static final int[][] ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY = new int[1][];
+    static {
+        ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY[0] = ALL_ZEROS_ARRAY;
+    }
+
+    private Class[/*number of args*/][/*arg index*/] unwrappingHintsByParamCount;
+    
+    /**
+     * Tells what types occur at a given parameter position with a bit field. See {@link TypeFlags}.
+     */
+    private int[/*number of args*/][/*arg index*/] typeFlagsByParamCount;
+    
+    // TODO: This can cause memory-leak when classes are re-loaded. However, first the genericClassIntrospectionCache
+    // and such need to be oms in this regard. 
+    private final Map/*<ArgumentTypes, MaybeEmptyCallableMemberDescriptor>*/ argTypesToMemberDescCache
+            = new ConcurrentHashMap(6, 0.75f, 1);
+    
+    private final List/*<ReflectionCallableMemberDescriptor>*/ memberDescs = new LinkedList();
+    
+    OverloadedMethodsSubset() {
+        //
+    }
+    
+    void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) {
+        memberDescs.add(memberDesc);
+        
+        // Warning: Do not modify this array, or put it into unwrappingHintsByParamCount by reference, as the arrays
+        // inside that are modified!
+        final Class[] prepedParamTypes = preprocessParameterTypes(memberDesc);
+        final int paramCount = prepedParamTypes.length;  // Must be the same as the length of the original param list
+        
+        // Merge these unwrapping hints with the existing table of hints:
+        if (unwrappingHintsByParamCount == null) {
+            unwrappingHintsByParamCount = new Class[paramCount + 1][];
+            unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
+        } else if (unwrappingHintsByParamCount.length <= paramCount) {
+            Class[][] newUnwrappingHintsByParamCount = new Class[paramCount + 1][];
+            System.arraycopy(unwrappingHintsByParamCount, 0, newUnwrappingHintsByParamCount, 0,
+                    unwrappingHintsByParamCount.length);
+            unwrappingHintsByParamCount = newUnwrappingHintsByParamCount;
+            unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
+        } else {
+            Class[] unwrappingHints = unwrappingHintsByParamCount[paramCount]; 
+            if (unwrappingHints == null) {
+                unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
+            } else {
+                for (int paramIdx = 0; paramIdx < prepedParamTypes.length; paramIdx++) {
+                    // For each parameter list length, we merge the argument type arrays into a single Class[] that
+                    // stores the most specific common types for each position. Hence we will possibly use a too generic
+                    // hint for the unwrapping. For correct behavior, for each overloaded methods its own parameter
+                    // types should be used as a hint. But without unwrapping the arguments, we couldn't select the
+                    // overloaded method. So we had to unwrap with all possible target types of each parameter position,
+                    // which would be slow and its result would be uncacheable (as we don't have anything usable as
+                    // a lookup key). So we just use this compromise.
+                    unwrappingHints[paramIdx] = getCommonSupertypeForUnwrappingHint(
+                            unwrappingHints[paramIdx], prepedParamTypes[paramIdx]);
+                }
+            }
+        }
+
+        int[] typeFlagsByParamIdx = ALL_ZEROS_ARRAY;
+        // Fill typeFlagsByParamCount (if necessary)
+        for (int paramIdx = 0; paramIdx < paramCount; paramIdx++) {
+            final int typeFlags = TypeFlags.classToTypeFlags(prepedParamTypes[paramIdx]);
+            if (typeFlags != 0) {
+                if (typeFlagsByParamIdx == ALL_ZEROS_ARRAY) {
+                    typeFlagsByParamIdx = new int[paramCount];
+                }
+                typeFlagsByParamIdx[paramIdx] = typeFlags;
+            }
+        }
+        mergeInTypesFlags(paramCount, typeFlagsByParamIdx);
+        
+        afterWideningUnwrappingHints(prepedParamTypes, typeFlagsByParamIdx);
+    }
+    
+    Class[][] getUnwrappingHintsByParamCount() {
+        return unwrappingHintsByParamCount;
+    }
+    
+    @SuppressFBWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
+            justification="Locks for member descriptor creation only")
+    final MaybeEmptyCallableMemberDescriptor getMemberDescriptorForArgs(Object[] args, boolean varArg) {
+        ArgumentTypes argTypes = new ArgumentTypes(args);
+        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 (argTypesToMemberDescCache) {
+                memberDesc = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes);
+                if (memberDesc == null) {
+                    memberDesc = argTypes.getMostSpecific(memberDescs, varArg);
+                    argTypesToMemberDescCache.put(argTypes, memberDesc);
+                }
+            }
+        }
+        return memberDesc;
+    }
+    
+    Iterator/*<ReflectionCallableMemberDescriptor>*/ getMemberDescriptors() {
+        return memberDescs.iterator();
+    }
+    
+    abstract Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc);
+    abstract void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes);
+    
+    abstract MaybeEmptyMemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs, 
+            DefaultObjectWrapper unwrapper) throws TemplateModelException;
+
+    /**
+     * Returns the most specific common class (or interface) of two parameter types for the purpose of unwrapping.
+     * This is trickier than finding the most specific overlapping superclass of two classes, because:
+     * <ul>
+     *   <li>It considers primitive classes as the subclasses of the boxing classes.</li>
+     *   <li>If the only common class is {@link Object}, it will try to find a common interface. If there are more
+     *       of them, it will start removing those that are known to be uninteresting as unwrapping hints.</li>
+     * </ul>
+     * 
+     * @param c1 Parameter type 1
+     * @param c2 Parameter type 2
+     */
+    protected Class getCommonSupertypeForUnwrappingHint(Class c1, Class c2) {
+        if (c1 == c2) return c1;
+        // This also means that the hint for (Integer, Integer) will be Integer, not just Number. This is consistent
+        // with how non-overloaded method hints work.
+        
+        // c1 primitive class to boxing class:
+        final boolean c1WasPrim; 
+        if (c1.isPrimitive()) {
+            c1 = _ClassUtil.primitiveClassToBoxingClass(c1);
+            c1WasPrim = true;
+        } else {
+            c1WasPrim = false;
+        }
+        
+        // c2 primitive class to boxing class:
+        final boolean c2WasPrim; 
+        if (c2.isPrimitive()) {
+            c2 = _ClassUtil.primitiveClassToBoxingClass(c2);
+            c2WasPrim = true;
+        } else {
+            c2WasPrim = false;
+        }
+
+        if (c1 == c2) {
+            // If it was like int and Integer, boolean and Boolean, etc., we return the boxing type (as that's the
+            // less specific, because it allows null.)
+            // (If it was two equivalent primitives, we don't get here, because of the 1st line of the method.) 
+            return c1;
+        } else if (Number.class.isAssignableFrom(c1) && Number.class.isAssignableFrom(c2)) {
+            // We don't want the unwrapper to convert to a numerical super-type [*] as it's not yet known what the
+            // actual number type of the chosen method will be. We will postpone the actual numerical conversion
+            // until that, especially as some conversions (like oms point to floating point) can be lossy.
+            // * Numerical super-type: Like long > int > short > byte.  
+            return Number.class;
+        } else if (c1WasPrim || c2WasPrim) {
+            // At this point these all stand:
+            // - At least one of them was primitive
+            // - No more than one of them was numerical
+            // - They don't have the same wrapper (boxing) class
+            return Object.class;
+        }
+        
+        // We never get to this point if buxfixed is true and any of these stands:
+        // - One of classes was a primitive type
+        // - One of classes was a numerical type (either boxing type or primitive)
+        
+        Set commonTypes = _MethodUtil.getAssignables(c1, c2);
+        commonTypes.retainAll(_MethodUtil.getAssignables(c2, c1));
+        if (commonTypes.isEmpty()) {
+            // Can happen when at least one of the arguments is an interface, as
+            // they don't have Object at the root of their hierarchy
+            return Object.class;
+        }
+        
+        // Gather maximally specific elements. Yes, there can be more than one 
+        // because of interfaces. I.e., if you call this method for String.class 
+        // and Number.class, you'll have Comparable, Serializable, and Object as 
+        // maximal elements. 
+        List max = new ArrayList();
+        listCommonTypes:  for (Iterator commonTypesIter = commonTypes.iterator(); commonTypesIter.hasNext(); ) {
+            Class clazz = (Class) commonTypesIter.next();
+            for (Iterator maxIter = max.iterator(); maxIter.hasNext(); ) {
+                Class maxClazz = (Class) maxIter.next();
+                if (_MethodUtil.isMoreOrSameSpecificParameterType(maxClazz, clazz, false /*bugfixed [1]*/, 0) != 0) {
+                    // clazz can't be maximal, if there's already a more specific or equal maximal than it.
+                    continue listCommonTypes;
+                }
+                if (_MethodUtil.isMoreOrSameSpecificParameterType(clazz, maxClazz, false /*bugfixed [1]*/, 0) != 0) {
+                    // If it's more specific than a currently maximal element,
+                    // that currently maximal is no longer a maximal.
+                    maxIter.remove();
+                }
+                // 1: We don't use bugfixed at the "[1]"-marked points because it's slower and doesn't make any
+                //    difference here as it's ensured that nor c1 nor c2 is primitive or numerical. The bugfix has only
+                //    affected the treatment of primitives and numerical types. 
+            }
+            // If we get here, no current maximal is more specific than the
+            // current class, so clazz is a new maximal so far.
+            max.add(clazz);
+        }
+        
+        if (max.size() > 1) {  // we have an ambiguity
+            // Find the non-interface class
+            for (Iterator it = max.iterator(); it.hasNext(); ) {
+                Class maxCl = (Class) it.next();
+                if (!maxCl.isInterface()) {
+                    if (maxCl != Object.class) {  // This actually shouldn't ever happen, but to be sure...
+                        // If it's not Object, we use it as the most specific
+                        return maxCl;
+                    } else {
+                        // Otherwise remove Object, and we will try with the interfaces 
+                        it.remove();
+                    }
+                }
+            }
+            
+            // At this point we only have interfaces left.
+            // Try removing interfaces about which we know that they are useless as unwrapping hints:
+            max.remove(Cloneable.class);
+            if (max.size() > 1) {  // Still have an ambiguity...
+                max.remove(Serializable.class);
+                if (max.size() > 1) {  // Still had an ambiguity...
+                    max.remove(Comparable.class);
+                    if (max.size() > 1) {
+                        return Object.class; // Still had an ambiguity... no luck.
+                    }
+                }
+            }
+        }
+        
+        return (Class) max.get(0);
+    }
+    
+    /**
+     * Gets the "type flags" of each parameter positions, or {@code null} if there's no method with this parameter
+     * count or if we are in pre-2.3.21 mode, or {@link #ALL_ZEROS_ARRAY} if there were no parameters that turned
+     * on a flag. The returned {@code int}-s are one or more {@link TypeFlags} constants binary "or"-ed together.  
+     */
+    final protected int[] getTypeFlags(int paramCount) {
+        return typeFlagsByParamCount != null && typeFlagsByParamCount.length > paramCount
+                ? typeFlagsByParamCount[paramCount]
+                : null;
+    }
+
+    /**
+     * Updates the content of the {@link #typeFlagsByParamCount} field with the parameter type flags of a method.
+     * 
+     * @param dstParamCount The parameter count for which we want to merge in the type flags 
+     * @param srcTypeFlagsByParamIdx If shorter than {@code dstParamCount}, its last item will be repeated until
+     *        dstParamCount length is reached. If longer, the excessive items will be ignored.
+     *        Maybe {@link #ALL_ZEROS_ARRAY}. Maybe a 0-length array. Can't be {@code null}.
+     */
+    final protected void mergeInTypesFlags(int dstParamCount, int[] srcTypeFlagsByParamIdx) {
+        _NullArgumentException.check("srcTypesFlagsByParamIdx", srcTypeFlagsByParamIdx);
+        
+        // Special case of 0 param count:
+        if (dstParamCount == 0) {
+            if (typeFlagsByParamCount == null) {
+                typeFlagsByParamCount = ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY;
+            } else if (typeFlagsByParamCount != ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY) {
+                typeFlagsByParamCount[0] = ALL_ZEROS_ARRAY;
+            }
+            return;
+        }
+        
+        // Ensure that typesFlagsByParamCount[dstParamCount] exists:
+        if (typeFlagsByParamCount == null) {
+            typeFlagsByParamCount = new int[dstParamCount + 1][];
+        } else if (typeFlagsByParamCount.length <= dstParamCount) {
+            int[][] newTypeFlagsByParamCount = new int[dstParamCount + 1][];
+            System.arraycopy(typeFlagsByParamCount, 0, newTypeFlagsByParamCount, 0,
+                    typeFlagsByParamCount.length);
+            typeFlagsByParamCount = newTypeFlagsByParamCount;
+        }
+        
+        int[] dstTypeFlagsByParamIdx = typeFlagsByParamCount[dstParamCount];
+        if (dstTypeFlagsByParamIdx == null) {
+            // This is the first method added with this number of params => no merging
+            
+            if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) {
+                int srcParamCount = srcTypeFlagsByParamIdx.length;
+                dstTypeFlagsByParamIdx = new int[dstParamCount];
+                for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
+                    dstTypeFlagsByParamIdx[paramIdx]
+                            = srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1];
+                }
+            } else {
+                dstTypeFlagsByParamIdx = ALL_ZEROS_ARRAY;
+            }
+            
+            typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx;
+        } else {
+            // dstTypeFlagsByParamIdx != null, so we need to merge into it.
+            
+            if (srcTypeFlagsByParamIdx == dstTypeFlagsByParamIdx) {
+                // Used to occur when both are ALL_ZEROS_ARRAY
+                return;
+            }
+            
+            // As we will write dstTypeFlagsByParamIdx, it can't remain ALL_ZEROS_ARRAY anymore. 
+            if (dstTypeFlagsByParamIdx == ALL_ZEROS_ARRAY && dstParamCount > 0) {
+                dstTypeFlagsByParamIdx = new int[dstParamCount];
+                typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx;
+            }
+            
+            for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
+                final int srcParamTypeFlags;
+                if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) {
+                    int srcParamCount = srcTypeFlagsByParamIdx.length;
+                    srcParamTypeFlags = srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1]; 
+                } else {
+                    srcParamTypeFlags = 0;
+                }
+                
+                final int dstParamTypesFlags = dstTypeFlagsByParamIdx[paramIdx];
+                if (dstParamTypesFlags != srcParamTypeFlags) {
+                    int mergedTypeFlags = dstParamTypesFlags | srcParamTypeFlags;
+                    if ((mergedTypeFlags & TypeFlags.MASK_ALL_NUMERICALS) != 0) {
+                        // Must not be set if we don't have numerical type at this index! 
+                        mergedTypeFlags |= TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT;
+                    }
+                    dstTypeFlagsByParamIdx[paramIdx] = mergedTypeFlags; 
+                }
+            }
+        }
+    }
+    
+    protected void forceNumberArgumentsToParameterTypes(
+            Object[] args, Class[] paramTypes, int[] typeFlagsByParamIndex) {
+        final int paramTypesLen = paramTypes.length;
+        final int argsLen = args.length;
+        for (int argIdx = 0; argIdx < argsLen; argIdx++) {
+            final int paramTypeIdx = argIdx < paramTypesLen ? argIdx : paramTypesLen - 1;
+            final int typeFlags = typeFlagsByParamIndex[paramTypeIdx];
+            
+            // Forcing the number type can only be interesting if there are numerical parameter types on that index,
+            // and the unwrapping was not to an exact numerical type.
+            if ((typeFlags & TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT) != 0) {
+                final Object arg = args[argIdx];
+                // If arg isn't a number, we can't do any conversions anyway, regardless of the param type.
+                if (arg instanceof Number) {
+                    final Class targetType = paramTypes[paramTypeIdx];
+                    final Number convertedArg = DefaultObjectWrapper.forceUnwrappedNumberToType((Number) arg, targetType);
+                    if (convertedArg != null) {
+                        args[argIdx] = convertedArg;
+                    }
+                }
+            }
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtil.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtil.java
new file mode 100644
index 0000000..0b05a6a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtil.java
@@ -0,0 +1,1289 @@
+/*
+ * 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.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._NumberUtil;
+
+/**
+ * Everything related to coercion to ambiguous numerical types.  
+ */
+class OverloadedNumberUtil {
+
+    // Can't be instantiated
+    private OverloadedNumberUtil() { }
+
+    /**
+     * The lower limit of conversion prices where there's a risk of significant mantissa loss.
+     * The value comes from misc/overloadedNumberRules/prices.ods and generator.ftl.
+     */
+    static final int BIG_MANTISSA_LOSS_PRICE = 4 * 10000;
+    
+    /** The highest long that can be stored in double without precision loss: 2**53. */
+    private static final long MAX_DOUBLE_OR_LONG = 9007199254740992L;
+    /** The lowest long that can be stored in double without precision loss: -(2**53). */
+    private static final long MIN_DOUBLE_OR_LONG = -9007199254740992L;
+    private static final int MAX_DOUBLE_OR_LONG_LOG_2 = 53;
+    
+    /** The highest long that can be stored in float without precision loss: 2**24. */
+    private static final int MAX_FLOAT_OR_INT = 16777216;
+    /** The lowest long that can be stored in float without precision loss: -(2**24). */
+    private static final int MIN_FLOAT_OR_INT = -16777216;
+    private static final int MAX_FLOAT_OR_INT_LOG_2 = 24;
+    /** Lowest number that we don't thread as possible integer 0. */
+    private static final double LOWEST_ABOVE_ZERO = 0.000001;
+    /** Highest number that we don't thread as possible integer 1. */
+    private static final double HIGHEST_BELOW_ONE = 0.999999;
+
+    /**
+     * Attaches the lowest alternative number type to the parameter number via {@link NumberWithFallbackType}, if
+     * that's useful according the possible target number types. This transformation is applied on the method call
+     * argument list before overloaded method selection.
+     * 
+     * <p>Note that as of this writing, this method is only used when
+     * {@link DefaultObjectWrapper#getIncompatibleImprovements()} >= 2.3.21.
+     * 
+     * <p>Why's this needed, how it works: Overloaded method selection only selects methods where the <em>type</em>
+     * (not the value!) of the argument is "smaller" or the same as the parameter type. This is similar to how it's in
+     * the Java language. That it only decides based on the parameter type is important because this way
+     * {@link OverloadedMethodsSubset} can cache method lookup decisions using the types as the cache key. Problem is,
+     * since you don't declare the exact numerical types in FTL, and FTL has only a single generic numeric type
+     * anyway, what Java type a {@link TemplateNumberModel} uses internally is often seen as a technical detail of which
+     * the template author can't always keep track of. So we investigate the <em>value</em> of the number too,
+     * then coerce it down without overflow to a type that will match the most overloaded methods. (This
+     * is especially important as FTL often stores numbers in {@link BigDecimal}-s, which will hardly ever match any
+     * method parameters.) We could simply return that number, like {@code Byte(0)} for an {@code Integer(0)},
+     * however, then we would lose the information about what the original type was. The original type is sometimes
+     * important, as in ambiguous situations the method where there's an exact type match should be selected (like,
+     * when someone wants to select an overload explicitly with {@code m(x?int)}). Also, if an overload wins where
+     * the parameter type at the position of the number is {@code Number} or {@code Object} (or {@code Comparable}
+     * etc.), it's expected that we pass in the original value (an {@code Integer} in this example), especially if that
+     * value is the return value of another Java method. That's why we use
+     * {@link NumberWithFallbackType} numerical classes like {@link IntegerOrByte}, which represents both the original
+     * type and the coerced type, all encoded into the class of the value, which is used as the overloaded method lookup
+     * cache key.
+     *  
+     * <p>See also: <tt>src\main\misc\overloadedNumberRules\prices.ods</tt>.
+     * 
+     * @param num the number to coerce
+     * @param typeFlags the type flags of the target parameter position; see {@link TypeFlags}
+     * 
+     * @returns The original number or a {@link NumberWithFallbackType}, depending on the actual value and the types
+     *     indicated in the {@code targetNumTypes} parameter.
+     */
+    static Number addFallbackType(final Number num, final int typeFlags) {
+        final Class numClass = num.getClass();
+        if (numClass == BigDecimal.class) {
+            // For now we only support the backward-compatible mode that doesn't prevent roll overs and magnitude loss.
+            // However, we push the overloaded selection to the right direction, so we will at least indicate if the
+            // number has decimals.
+            BigDecimal n = (BigDecimal) num; 
+            if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) != 0
+                    && (typeFlags & TypeFlags.MASK_KNOWN_NONINTEGERS) != 0
+                    && _NumberUtil.isIntegerBigDecimal(n) /* <- can be expensive */) {
+                return new IntegerBigDecimal(n);
+            } else {
+                // Either it was a non-integer, or it didn't mater what it was, as we don't have both integer and
+                // non-integer target types. 
+                return n;
+            }
+        } else if (numClass == Integer.class) {
+            int pn = num.intValue();
+            // Note that we try to return the most specific type (i.e., the numerical type with the smallest range), but
+            // only among the types that are possible targets. Like if the only target is int and the value is 1, we
+            // will return Integer 1, not Byte 1, even though byte is automatically converted to int so it would
+            // work too. Why we avoid unnecessarily specific types is that they generate more overloaded method lookup
+            // cache entries, since the cache key is the array of the types of the argument values. So we want as few
+            // permutations as possible. 
+            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
+                return new IntegerOrByte((Integer) num, (byte) pn);
+            } else if ((typeFlags & TypeFlags.SHORT) != 0 && pn <= Short.MAX_VALUE && pn >= Short.MIN_VALUE) {
+                return new IntegerOrShort((Integer) num, (short) pn);
+            } else {
+                return num;
+            }
+        } else if (numClass == Long.class) {
+            final long pn = num.longValue(); 
+            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
+                return new LongOrByte((Long) num, (byte) pn);
+            } else if ((typeFlags & TypeFlags.SHORT) != 0 && pn <= Short.MAX_VALUE && pn >= Short.MIN_VALUE) {
+                return new LongOrShort((Long) num, (short) pn);
+            } else if ((typeFlags & TypeFlags.INTEGER) != 0 && pn <= Integer.MAX_VALUE && pn >= Integer.MIN_VALUE) {
+                return new LongOrInteger((Long) num, (int) pn);
+            } else {
+                return num;
+            }
+        } else if (numClass == Double.class) {
+            final double doubleN = num.doubleValue();
+            
+            // Can we store it in an integer type?
+            checkIfWholeNumber: do {
+                if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) == 0) break checkIfWholeNumber;
+                
+                // There's no hope to be 1-precise outside this region. (Although problems can occur even inside it...)
+                if (doubleN > MAX_DOUBLE_OR_LONG || doubleN < MIN_DOUBLE_OR_LONG) break checkIfWholeNumber;
+                
+                long longN = num.longValue(); 
+                double diff = doubleN - longN;
+                boolean exact;  // We will try to ignore precision glitches (like 0.3 - 0.2 - 0.1 = -2.7E-17)
+                if (diff == 0) {
+                    exact = true;
+                } else if (diff > 0) {
+                    if (diff < LOWEST_ABOVE_ZERO) {
+                        exact = false;
+                    } else if (diff > HIGHEST_BELOW_ONE) {
+                        exact = false;
+                        longN++;
+                    } else {
+                        break checkIfWholeNumber;
+                    }
+                } else {  // => diff < 0
+                    if (diff > -LOWEST_ABOVE_ZERO) {
+                        exact = false;
+                    } else if (diff < -HIGHEST_BELOW_ONE) {
+                        exact = false;
+                        longN--;
+                    } else {
+                        break checkIfWholeNumber;
+                    }
+                }
+                
+                // If we reach this, it can be treated as a whole number.
+                
+                if ((typeFlags & TypeFlags.BYTE) != 0
+                        && longN <= Byte.MAX_VALUE && longN >= Byte.MIN_VALUE) {
+                    return new DoubleOrByte((Double) num, (byte) longN);
+                } else if ((typeFlags & TypeFlags.SHORT) != 0
+                        && longN <= Short.MAX_VALUE && longN >= Short.MIN_VALUE) {
+                    return new DoubleOrShort((Double) num, (short) longN);
+                } else if ((typeFlags & TypeFlags.INTEGER) != 0
+                        && longN <= Integer.MAX_VALUE && longN >= Integer.MIN_VALUE) {
+                    final int intN = (int) longN; 
+                    return (typeFlags & TypeFlags.FLOAT) != 0 && intN >= MIN_FLOAT_OR_INT && intN <= MAX_FLOAT_OR_INT
+                                    ? new DoubleOrIntegerOrFloat((Double) num, intN)
+                                    : new DoubleOrInteger((Double) num, intN);
+                } else if ((typeFlags & TypeFlags.LONG) != 0) {
+                    if (exact) {
+                        return new DoubleOrLong((Double) num, longN);
+                    } else {
+                        // We don't deal with non-exact numbers outside the range of int, as we already reach
+                        // ULP 2.384185791015625E-7 there.
+                        if (longN >= Integer.MIN_VALUE && longN <= Integer.MAX_VALUE) {
+                            return new DoubleOrLong((Double) num, longN);
+                        } else {
+                            break checkIfWholeNumber;
+                        }
+                    }
+                }
+                // This point is reached if the double value was out of the range of target integer type(s). 
+                // Falls through!
+            } while (false);
+            // If we reach this that means that it can't be treated as a whole number.
+            
+            if ((typeFlags & TypeFlags.FLOAT) != 0 && doubleN >= -Float.MAX_VALUE && doubleN <= Float.MAX_VALUE) {
+                return new DoubleOrFloat((Double) num);
+            } else {
+                // Simply Double:
+                return num;
+            }
+        } else if (numClass == Float.class) {
+            final float floatN = num.floatValue();
+            
+            // Can we store it in an integer type?
+            checkIfWholeNumber: do {
+                if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) == 0) break checkIfWholeNumber;
+                
+                // There's no hope to be 1-precise outside this region. (Although problems can occur even inside it...)
+                if (floatN > MAX_FLOAT_OR_INT || floatN < MIN_FLOAT_OR_INT) break checkIfWholeNumber;
+                
+                int intN = num.intValue();
+                double diff = floatN - intN;
+                boolean exact;  // We will try to ignore precision glitches (like 0.3 - 0.2 - 0.1 = -2.7E-17)
+                if (diff == 0) {
+                    exact = true;
+                // We already reach ULP 7.6293945E-6 with bytes, so we don't continue with shorts.
+                } else if (intN >= Byte.MIN_VALUE && intN <= Byte.MAX_VALUE) {
+                    if (diff > 0) {
+                        if (diff < 0.00001) {
+                            exact = false;
+                        } else if (diff > 0.99999) {
+                            exact = false;
+                            intN++;
+                        } else {
+                            break checkIfWholeNumber;
+                        }
+                    } else {  // => diff < 0
+                        if (diff > -0.00001) {
+                            exact = false;
+                        } else if (diff < -0.99999) {
+                            exact = false;
+                            intN--;
+                        } else {
+                            break checkIfWholeNumber;
+                        }
+                    }
+                } else {
+                    break checkIfWholeNumber;
+                }
+                
+                // If we reach this, it can be treated as a whole number.
+                
+                if ((typeFlags & TypeFlags.BYTE) != 0 && intN <= Byte.MAX_VALUE && intN >= Byte.MIN_VALUE) {
+                    return new FloatOrByte((Float) num, (byte) intN);
+                } else if ((typeFlags & TypeFlags.SHORT) != 0 && intN <= Short.MAX_VALUE && intN >= Short.MIN_VALUE) {
+                    return new FloatOrShort((Float) num, (short) intN);
+                } else if ((typeFlags & TypeFlags.INTEGER) != 0) {
+                    return new FloatOrInteger((Float) num, intN);
+                } else if ((typeFlags & TypeFlags.LONG) != 0) {
+                    // We can't even go outside the range of integers, so we don't need Long variation:
+                    return exact
+                            ? new FloatOrInteger((Float) num, intN)
+                            : new FloatOrByte((Float) num, (byte) intN);  // as !exact implies (-128..127)
+                }
+                // This point is reached if the float value was out of the range of target integer type(s). 
+                // Falls through!
+            } while (false);
+            // If we reach this that means that it can't be treated as a whole number. So it's simply a Float:
+            return num;
+        } else if (numClass == Byte.class) {
+            return num;
+        } else if (numClass == Short.class) {
+            short pn = num.shortValue(); 
+            if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
+                return new ShortOrByte((Short) num, (byte) pn);
+            } else {
+                return num;
+            }
+        } else if (numClass == BigInteger.class) {
+            if ((typeFlags
+                    & ((TypeFlags.MASK_KNOWN_INTEGERS | TypeFlags.MASK_KNOWN_NONINTEGERS)
+                            ^ (TypeFlags.BIG_INTEGER | TypeFlags.BIG_DECIMAL))) != 0) {
+                BigInteger biNum = (BigInteger) num;
+                final int bitLength = biNum.bitLength();  // Doesn't include sign bit, so it's one less than expected
+                if ((typeFlags & TypeFlags.BYTE) != 0 && bitLength <= 7) {
+                    return new BigIntegerOrByte(biNum);
+                } else if ((typeFlags & TypeFlags.SHORT) != 0 && bitLength <= 15) {
+                    return new BigIntegerOrShort(biNum);
+                } else if ((typeFlags & TypeFlags.INTEGER) != 0 && bitLength <= 31) {
+                    return new BigIntegerOrInteger(biNum);
+                } else if ((typeFlags & TypeFlags.LONG) != 0 && bitLength <= 63) {
+                    return new BigIntegerOrLong(biNum);
+                } else if ((typeFlags & TypeFlags.FLOAT) != 0
+                        && (bitLength <= MAX_FLOAT_OR_INT_LOG_2
+                            || bitLength == MAX_FLOAT_OR_INT_LOG_2 + 1
+                               && biNum.getLowestSetBit() >= MAX_FLOAT_OR_INT_LOG_2)) {
+                    return new BigIntegerOrFloat(biNum);
+                } else if ((typeFlags & TypeFlags.DOUBLE) != 0
+                        && (bitLength <= MAX_DOUBLE_OR_LONG_LOG_2
+                            || bitLength == MAX_DOUBLE_OR_LONG_LOG_2 + 1
+                               && biNum.getLowestSetBit() >= MAX_DOUBLE_OR_LONG_LOG_2)) {
+                    return new BigIntegerOrDouble(biNum);
+                } else {
+                    return num;
+                }
+            } else {
+                // No relevant coercion target types; return the BigInteger as is:
+                return num;
+            }
+        } else {
+            // Unknown number type:
+            return num;
+        }
+    }
+
+    interface ByteSource { Byte byteValue(); }
+    interface ShortSource { Short shortValue(); }
+    interface IntegerSource { Integer integerValue(); }
+    interface LongSource { Long longValue(); }
+    interface FloatSource { Float floatValue(); }
+    interface DoubleSource { Double doubleValue(); }
+    interface BigIntegerSource { BigInteger bigIntegerValue(); }
+    interface BigDecimalSource { BigDecimal bigDecimalValue(); }
+    
+    /**
+     * Superclass of "Or"-ed numerical types. With an example, a {@code int} 1 has the fallback type {@code byte}, as
+     * that's the smallest type that can store the value, so it can be represented as an {@link IntegerOrByte}.
+     * This is useful as overloaded method selection only examines the type of the arguments, not the value of them,
+     * but with "Or"-ed types we can encode this value-related information into the argument type, hence influencing the
+     * method selection.
+     */
+    abstract static class NumberWithFallbackType extends Number implements Comparable {
+        
+        protected abstract Number getSourceNumber();
+
+        @Override
+        public int intValue() {
+            return getSourceNumber().intValue();
+        }
+
+        @Override
+        public long longValue() {
+            return getSourceNumber().longValue();
+        }
+
+        @Override
+        public float floatValue() {
+            return getSourceNumber().floatValue();
+        }
+
+        @Override
+        public double doubleValue() {
+            return getSourceNumber().doubleValue();
+        }
+
+        @Override
+        public byte byteValue() {
+            return getSourceNumber().byteValue();
+        }
+
+        @Override
+        public short shortValue() {
+            return getSourceNumber().shortValue();
+        }
+
+        @Override
+        public int hashCode() {
+            return getSourceNumber().hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj != null && getClass() == obj.getClass()) {
+                return getSourceNumber().equals(((NumberWithFallbackType) obj).getSourceNumber());
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return getSourceNumber().toString();
+        }
+
+        // We have to implement this, so that if a potential matching method expects a Comparable, which is implemented
+        // by all the supported numerical types, the "Or" type will be a match. 
+        @Override
+        public int compareTo(Object o) {
+            Number n = getSourceNumber();
+            if (n instanceof Comparable) {
+                return ((Comparable) n).compareTo(o); 
+            } else {
+                throw new ClassCastException(n.getClass().getName() + " is not Comparable.");
+            }
+        }
+        
+    }
+
+    /**
+     * Holds a {@link BigDecimal} that stores a whole number. When selecting a overloaded method, FreeMarker tries to
+     * associate {@link BigDecimal} values to parameters of types that can hold non-whole numbers, unless the
+     * {@link BigDecimal} is wrapped into this class, in which case it does the opposite. This mechanism is, however,
+     * too rough to prevent roll overs or magnitude losses. Those are not yet handled for backward compatibility (they
+     * were suppressed earlier too).
+     */
+    static final class IntegerBigDecimal extends NumberWithFallbackType {
+
+        private final BigDecimal n;
+        
+        IntegerBigDecimal(BigDecimal n) {
+            this.n = n;
+        }
+
+        @Override
+        protected Number getSourceNumber() {
+            return n;
+        }
+        
+        public BigInteger bigIntegerValue() {
+            return n.toBigInteger();
+        }
+        
+    }
+
+    static abstract class LongOrSmallerInteger extends NumberWithFallbackType {
+        
+        private final Long n;
+        
+        protected LongOrSmallerInteger(Long n) {
+            this.n = n;
+        }
+
+        @Override
+        protected Number getSourceNumber() {
+            return n;
+        }
+
+        @Override
+        public long longValue() {
+            return n.longValue();
+        }
+        
+    }
+    
+    static class LongOrByte extends LongOrSmallerInteger {
+        
+        private final byte w; 
+
+        LongOrByte(Long n, byte w) {
+            super(n);
+            this.w = w;
+        }
+
+        @Override
+        public byte byteValue() {
+            return w;
+        }
+        
+    }
+    
+    static class LongOrShort extends LongOrSmallerInteger {
+        
+        private final short w; 
+
+        LongOrShort(Long n, short w) {
+            super(n);
+            this.w = w;
+        }
+
+        @Override
+        public short shortValue() {
+            return w;
+        }
+        
+    }
+    
+    static class LongOrInteger extends LongOrSmallerInteger {
+        
+        private final int w; 
+
+        LongOrInteger(Long n, int w) {
+            super(n);
+            this.w = w;
+        }
+
+        @Override
+        public int intValue() {
+            return w;
+        }
+        
+    }
+    
+    static abstract class IntegerOrSmallerInteger extends NumberWithFallbackType {
+        
+        private final Integer n;
+        
+        protected IntegerOrSmallerInteger(Integer n) {
+            this.n = n;
+        }
+
+        @Override
+        protected Number getSourceNumber() {
+            return n;
+        }
+
+        @Override
+        public int intValue() {
+            return n.intValue();
+        }
+        
+    }
+    
+    static class IntegerOrByte extends IntegerOrSmallerInteger {
+        
+        private final byte w; 
+
+        IntegerOrByte(Integer n, byte w) {
+            super(n);
+            this.w = w;
+        }
+
+        @Override
+        public byte byteValue() {
+            return w;
+        }
+        
+    }
+    
+    static class IntegerOrShort extends IntegerOrSmallerInteger {
+        
+        private final short w; 
+
+        IntegerOrShort(Integer n, short w) {
+            super(n);
+            this.w = w;
+        }
+
+        @Override
+        public short shortValue() {
+            return w;
+        }
+        
+    }
+    
+    static class ShortOrByte extends NumberWithFallbackType {
+        
+        private final Short n;
+        private final byte w;
+        
+        protected ShortOrByte(Short n, byte w) {
+            this.n = n;
+            this.w = w;
+        }
+
+        @Override
+        protected Number getSourceNumber() {
+            return n;
+        }
+
+        @Override
+        public short shortValue() {
+            return n.shortValue();
+        }
+
+        @Override
+        public byte byteValue() {
+            return w;
+        }
+        
+    }
+    
+    static abstract class DoubleOrWholeNumber extends NumberWithFallbackType {
+        
+        private final Double n; 
+
+        protected DoubleOrWholeNumber(Double n) {
+            this.n = n;
+        }
+
+        @Override
+        protected Number getSourceNumber() {
+            return n;
+        }
+        
+        @Override
+        public double doubleValue() {
+            return n.doubleValue();
+        }
+        
+    }
+    
+    static final class DoubleOrByte extends DoubleOrWholeNumber {
+        
+        private final byte w;
+
+        DoubleOrByte(Double n, byte w) {
+            super(n);
+            this.w = w;
+        }
+        
+        @Override
+        public byte byteValue() {
+            return w;
+        }
+        
+        @Override
+        public short shortValue() {
+            return w;
+        }
+        
+        @Override
+        public int intValue() {
+            return w;
+        }
+        
+        @Override
+        public long longValue() {
+            return w;
+        }
+        
+    }
+    
+    static final class DoubleOrShort extends DoubleOrWholeNumber {
+        
+        private final short w;
+
+        DoubleOrShort(Double n, short w) {
+            super(n);
+            this.w = w;
+        }
+        
+        @Override
+        public short shortValue() {
+            return w;
+        }
+        
+        @Override
+        public int intValue() {
+            return w;
+        }
+        
+        @Override
+        public long longValue() {
+            return w;
+        }
+        
+    }
+    
+    static final class DoubleOrIntegerOrFloat extends DoubleOrWholeNumber {
+
+        private final int w;
+
+        DoubleOrIntegerOrFloat(Double n, int w) {
+            super(n);
+            this.w = w;
+        }
+        
+        @Override
+        public int intValue() {
+            return w;
+        }
+        
+        @Override
+        public long longValue() {
+            return w;
+        }
+        
+    }
+    
+    static final class DoubleOrInteger extends DoubleOrWholeNumber {
+
+        private final int w;
+
+        DoubleOrInteger(Double n, int w) {
+            super(n);
+            this.w = w;
+        }
+        
+        @Override
+        public int intValue() {
+            return w;
+        }
+        
+        @Override
+        public long longValue() {
+            return w;
+        }
+        
+    }
+    
+    static final class DoubleOrLong extends DoubleOrWholeNumber {
+
+        private final long w;
+
+        DoubleOrLong(Double n, long w) {
+            super(n);
+            this.w = w;
+        }
+        
+        @Override
+        public long longValue() {
+            return w;
+        }
+        
+    }
+    
+    static final class DoubleOrFloat extends NumberWithFallbackType {
+        
+        private final Double n;
+
+        DoubleOrFloat(Double n) {
+            this.n = n;
+        }
+        
+        @Override
+        public float floatValue() {
+            return n.floatValue();
+        }
+        
+        @Override
+        public double doubleValue() {
+            return n.doubleValue();
+        }
+
+        @Override
+        protected Number getSourceNumber() {
+            return n;
+        }
+        
+    }
+
+    static abstract class FloatOrWholeNumber extends NumberWithFallbackType {
+        
+        private final Float n; 
+
+        FloatOrWholeNumber(Float n) {
+            this.n = n;
+        }
+
+        @Override
+        protected Number getSourceNumber() {
+            return n;
+        }
+        
+        @Override
+        public float floatValue() {
+            return n.floatValue();
+        }
+        
+    }
+    
+    static final class FloatOrByte extends FloatOrWholeNumber {
+        
+        private final byte w;
+
+        FloatOrByte(Float n, byte w) {
+            super(n);
+            this.w = w;
+        }
+        
+        @Override
+        public byte byteValue() {
+            return w;
+        }
+        
+        @Override
+        public short shortValue() {
+            return w;
+        }
+        
+        @Override
+        public int intValue() {
+            return w;
+        }
+        
+        @Override
+        public long longValue() {
+            return w;
+        }
+        
+    }
+    
+    static final class FloatOrShort extends FloatOrWholeNumber {
+        
+        private final short w;
+
+        FloatOrShort(Float n, short w) {
+            super(n);
+            this.w = w;
+        }
+        
+        @Override
+        public short shortValue() {
+            return w;
+        }
+        
+        @Override
+        public int intValue() {
+            return w;
+        }
+        
+        @Override
+        public long longValue() {
+            return w;
+        }
+        
+    }
+
+    static final class FloatOrInteger extends FloatOrWholeNumber {
+        
+        private final int w;
+
+        FloatOrInteger(Float n, int w) {
+            super(n);
+            this.w = w;
+        }
+        
+        @Override
+        public int intValue() {
+            return w;
+        }
+        
+        @Override
+        public long longValue() {
+            return w;
+        }
+        
+    }
+
+    abstract static class BigIntegerOrPrimitive extends NumberWithFallbackType {
+
+        protected final BigInteger n;
+        
+        BigIntegerOrPrimitive(BigInteger n) {
+            this.n = n;
+        }
+
+        @Override
+        protected Number getSourceNumber() {
+            return n;
+        }
+        
+    }
+    
+    final static class BigIntegerOrByte extends BigIntegerOrPrimitive {
+
+        BigIntegerOrByte(BigInteger n) {
+            super(n);
+        }
+
+    }
+    
+    final static class BigIntegerOrShort extends BigIntegerOrPrimitive {
+
+        BigIntegerOrShort(BigInteger n) {
+            super(n);
+        }
+
+    }
+    
+    final static class BigIntegerOrInteger extends BigIntegerOrPrimitive {
+
+        BigIntegerOrInteger(BigInteger n) {
+            super(n);
+        }
+
+    }
+    
+    final static class BigIntegerOrLong extends BigIntegerOrPrimitive {
+
+        BigIntegerOrLong(BigInteger n) {
+            super(n);
+        }
+
+    }
+
+    abstract static class BigIntegerOrFPPrimitive extends BigIntegerOrPrimitive {
+
+        BigIntegerOrFPPrimitive(BigInteger n) {
+            super(n);
+        }
+
+        /** Faster version of {@link BigDecimal#floatValue()}, utilizes that the number known to fit into a long. */
+        @Override
+        public float floatValue() {
+            return n.longValue(); 
+        }
+        
+        /** Faster version of {@link BigDecimal#doubleValue()}, utilizes that the number known to fit into a long. */
+        @Override
+        public double doubleValue() {
+            return n.longValue(); 
+        }
+
+    }
+    
+    final static class BigIntegerOrFloat extends BigIntegerOrFPPrimitive {
+
+        BigIntegerOrFloat(BigInteger n) {
+            super(n);
+        }
+
+    }
+    
+    final static class BigIntegerOrDouble extends BigIntegerOrFPPrimitive {
+
+        BigIntegerOrDouble(BigInteger n) {
+            super(n);
+        }
+        
+    }
+    
+    /**
+     * Returns a non-negative number that indicates how much we want to avoid a given numerical type conversion. Since
+     * we only consider the types here, not the actual value, we always consider the worst case scenario. Like it will
+     * say that converting int to short is not allowed, although int 1 can be converted to byte without loss. To account
+     * for such situations, "Or"-ed types, like {@link IntegerOrByte} has to be used. 
+     * 
+     * @param fromC the non-primitive type of the argument (with other words, the actual type).
+     *        Must be {@link Number} or its subclass. This is possibly an {@link NumberWithFallbackType} subclass.
+     * @param toC the <em>non-primitive</em> type of the target parameter (with other words, the format type).
+     *        Must be a {@link Number} subclass, not {@link Number} itself.
+     *        Must <em>not</em> be {@link NumberWithFallbackType} or its subclass.
+     * 
+     * @return
+     *     <p>The possible values are:
+     *     <ul>
+     *       <li>0: No conversion is needed
+     *       <li>[0, 30000): Lossless conversion
+     *       <li>[30000, 40000): Smaller precision loss in mantissa is possible.
+     *       <li>[40000, 50000): Bigger precision loss in mantissa is possible.
+     *       <li>{@link Integer#MAX_VALUE}: Conversion not allowed due to the possibility of magnitude loss or
+     *          overflow</li>
+     *     </ul>
+     * 
+     *     <p>At some places, we only care if the conversion is possible, i.e., whether the return value is
+     *     {@link Integer#MAX_VALUE} or not. But when multiple overloaded methods have an argument type to which we
+     *     could convert to, this number will influence which of those will be chosen.
+     */
+    static int getArgumentConversionPrice(Class fromC, Class toC) {
+        // DO NOT EDIT, generated code!
+        // See: src\main\misc\overloadedNumberRules\README.txt
+        if (toC == fromC) {
+            return 0;
+        } else if (toC == Integer.class) {
+            if (fromC == IntegerBigDecimal.class) return 31003;
+            else if (fromC == BigDecimal.class) return 41003;
+            else if (fromC == Long.class) return Integer.MAX_VALUE;
+            else if (fromC == Double.class) return Integer.MAX_VALUE;
+            else if (fromC == Float.class) return Integer.MAX_VALUE;
+            else if (fromC == Byte.class) return 10003;
+            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == LongOrInteger.class) return 21003;
+            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrIntegerOrFloat.class) return 22003;
+            else if (fromC == DoubleOrInteger.class) return 22003;
+            else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
+            else if (fromC == IntegerOrByte.class) return 0;
+            else if (fromC == DoubleOrByte.class) return 22003;
+            else if (fromC == LongOrByte.class) return 21003;
+            else if (fromC == Short.class) return 10003;
+            else if (fromC == LongOrShort.class) return 21003;
+            else if (fromC == ShortOrByte.class) return 10003;
+            else if (fromC == FloatOrInteger.class) return 21003;
+            else if (fromC == FloatOrByte.class) return 21003;
+            else if (fromC == FloatOrShort.class) return 21003;
+            else if (fromC == BigIntegerOrInteger.class) return 16003;
+            else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrByte.class) return 16003;
+            else if (fromC == IntegerOrShort.class) return 0;
+            else if (fromC == DoubleOrShort.class) return 22003;
+            else if (fromC == BigIntegerOrShort.class) return 16003;
+            else return Integer.MAX_VALUE;
+        } else if (toC == Long.class) {
+            if (fromC == Integer.class) return 10004;
+            else if (fromC == IntegerBigDecimal.class) return 31004;
+            else if (fromC == BigDecimal.class) return 41004;
+            else if (fromC == Double.class) return Integer.MAX_VALUE;
+            else if (fromC == Float.class) return Integer.MAX_VALUE;
+            else if (fromC == Byte.class) return 10004;
+            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == LongOrInteger.class) return 0;
+            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrIntegerOrFloat.class) return 21004;
+            else if (fromC == DoubleOrInteger.class) return 21004;
+            else if (fromC == DoubleOrLong.class) return 21004;
+            else if (fromC == IntegerOrByte.class) return 10004;
+            else if (fromC == DoubleOrByte.class) return 21004;
+            else if (fromC == LongOrByte.class) return 0;
+            else if (fromC == Short.class) return 10004;
+            else if (fromC == LongOrShort.class) return 0;
+            else if (fromC == ShortOrByte.class) return 10004;
+            else if (fromC == FloatOrInteger.class) return 21004;
+            else if (fromC == FloatOrByte.class) return 21004;
+            else if (fromC == FloatOrShort.class) return 21004;
+            else if (fromC == BigIntegerOrInteger.class) return 15004;
+            else if (fromC == BigIntegerOrLong.class) return 15004;
+            else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrByte.class) return 15004;
+            else if (fromC == IntegerOrShort.class) return 10004;
+            else if (fromC == DoubleOrShort.class) return 21004;
+            else if (fromC == BigIntegerOrShort.class) return 15004;
+            else return Integer.MAX_VALUE;
+        } else if (toC == Double.class) {
+            if (fromC == Integer.class) return 20007;
+            else if (fromC == IntegerBigDecimal.class) return 32007;
+            else if (fromC == BigDecimal.class) return 32007;
+            else if (fromC == Long.class) return 30007;
+            else if (fromC == Float.class) return 10007;
+            else if (fromC == Byte.class) return 20007;
+            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == LongOrInteger.class) return 21007;
+            else if (fromC == DoubleOrFloat.class) return 0;
+            else if (fromC == DoubleOrIntegerOrFloat.class) return 0;
+            else if (fromC == DoubleOrInteger.class) return 0;
+            else if (fromC == DoubleOrLong.class) return 0;
+            else if (fromC == IntegerOrByte.class) return 20007;
+            else if (fromC == DoubleOrByte.class) return 0;
+            else if (fromC == LongOrByte.class) return 21007;
+            else if (fromC == Short.class) return 20007;
+            else if (fromC == LongOrShort.class) return 21007;
+            else if (fromC == ShortOrByte.class) return 20007;
+            else if (fromC == FloatOrInteger.class) return 10007;
+            else if (fromC == FloatOrByte.class) return 10007;
+            else if (fromC == FloatOrShort.class) return 10007;
+            else if (fromC == BigIntegerOrInteger.class) return 20007;
+            else if (fromC == BigIntegerOrLong.class) return 30007;
+            else if (fromC == BigIntegerOrDouble.class) return 20007;
+            else if (fromC == BigIntegerOrFloat.class) return 20007;
+            else if (fromC == BigIntegerOrByte.class) return 20007;
+            else if (fromC == IntegerOrShort.class) return 20007;
+            else if (fromC == DoubleOrShort.class) return 0;
+            else if (fromC == BigIntegerOrShort.class) return 20007;
+            else return Integer.MAX_VALUE;
+        } else if (toC == Float.class) {
+            if (fromC == Integer.class) return 30006;
+            else if (fromC == IntegerBigDecimal.class) return 33006;
+            else if (fromC == BigDecimal.class) return 33006;
+            else if (fromC == Long.class) return 40006;
+            else if (fromC == Double.class) return Integer.MAX_VALUE;
+            else if (fromC == Byte.class) return 20006;
+            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == LongOrInteger.class) return 30006;
+            else if (fromC == DoubleOrFloat.class) return 30006;
+            else if (fromC == DoubleOrIntegerOrFloat.class) return 23006;
+            else if (fromC == DoubleOrInteger.class) return 30006;
+            else if (fromC == DoubleOrLong.class) return 40006;
+            else if (fromC == IntegerOrByte.class) return 24006;
+            else if (fromC == DoubleOrByte.class) return 23006;
+            else if (fromC == LongOrByte.class) return 24006;
+            else if (fromC == Short.class) return 20006;
+            else if (fromC == LongOrShort.class) return 24006;
+            else if (fromC == ShortOrByte.class) return 20006;
+            else if (fromC == FloatOrInteger.class) return 0;
+            else if (fromC == FloatOrByte.class) return 0;
+            else if (fromC == FloatOrShort.class) return 0;
+            else if (fromC == BigIntegerOrInteger.class) return 30006;
+            else if (fromC == BigIntegerOrLong.class) return 40006;
+            else if (fromC == BigIntegerOrDouble.class) return 40006;
+            else if (fromC == BigIntegerOrFloat.class) return 24006;
+            else if (fromC == BigIntegerOrByte.class) return 24006;
+            else if (fromC == IntegerOrShort.class) return 24006;
+            else if (fromC == DoubleOrShort.class) return 23006;
+            else if (fromC == BigIntegerOrShort.class) return 24006;
+            else return Integer.MAX_VALUE;
+        } else if (toC == Byte.class) {
+            if (fromC == Integer.class) return Integer.MAX_VALUE;
+            else if (fromC == IntegerBigDecimal.class) return 35001;
+            else if (fromC == BigDecimal.class) return 45001;
+            else if (fromC == Long.class) return Integer.MAX_VALUE;
+            else if (fromC == Double.class) return Integer.MAX_VALUE;
+            else if (fromC == Float.class) return Integer.MAX_VALUE;
+            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == LongOrInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrIntegerOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
+            else if (fromC == IntegerOrByte.class) return 22001;
+            else if (fromC == DoubleOrByte.class) return 25001;
+            else if (fromC == LongOrByte.class) return 23001;
+            else if (fromC == Short.class) return Integer.MAX_VALUE;
+            else if (fromC == LongOrShort.class) return Integer.MAX_VALUE;
+            else if (fromC == ShortOrByte.class) return 21001;
+            else if (fromC == FloatOrInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == FloatOrByte.class) return 23001;
+            else if (fromC == FloatOrShort.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrByte.class) return 18001;
+            else if (fromC == IntegerOrShort.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrShort.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrShort.class) return Integer.MAX_VALUE;
+            else return Integer.MAX_VALUE;
+        } else if (toC == Short.class) {
+            if (fromC == Integer.class) return Integer.MAX_VALUE;
+            else if (fromC == IntegerBigDecimal.class) return 34002;
+            else if (fromC == BigDecimal.class) return 44002;
+            else if (fromC == Long.class) return Integer.MAX_VALUE;
+            else if (fromC == Double.class) return Integer.MAX_VALUE;
+            else if (fromC == Float.class) return Integer.MAX_VALUE;
+            else if (fromC == Byte.class) return 10002;
+            else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == LongOrInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrIntegerOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
+            else if (fromC == IntegerOrByte.class) return 21002;
+            else if (fromC == DoubleOrByte.class) return 24002;
+            else if (fromC == LongOrByte.class) return 22002;
+            else if (fromC == LongOrShort.class) return 22002;
+            else if (fromC == ShortOrByte.class) return 0;
+            else if (fromC == FloatOrInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == FloatOrByte.class) return 22002;
+            else if (fromC == FloatOrShort.class) return 22002;
+            else if (fromC == BigIntegerOrInteger.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == BigIntegerOrByte.class) return 17002;
+            else if (fromC == IntegerOrShort.class) return 21002;
+            else if (fromC == DoubleOrShort.class) return 24002;
+            else if (fromC == BigIntegerOrShort.class) return 17002;
+            else return Integer.MAX_VALUE;
+        } else if (toC == BigDecimal.class) {
+            if (fromC == Integer.class) return 20008;
+            else if (fromC == IntegerBigDecimal.class) return 0;
+            else if (fromC == Long.class) return 20008;
+            else if (fromC == Double.class) return 20008;
+            else if (fromC == Float.class) return 20008;
+            else if (fromC == Byte.class) return 20008;
+            else if (fromC == BigInteger.class) return 10008;
+            else if (fromC == LongOrInteger.class) return 20008;
+            else if (fromC == DoubleOrFloat.class) return 20008;
+            else if (fromC == DoubleOrIntegerOrFloat.class) return 20008;
+            else if (fromC == DoubleOrInteger.class) return 20008;
+            else if (fromC == DoubleOrLong.class) return 20008;
+            else if (fromC == IntegerOrByte.class) return 20008;
+            else if (fromC == DoubleOrByte.class) return 20008;
+            else if (fromC == LongOrByte.class) return 20008;
+            else if (fromC == Short.class) return 20008;
+            else if (fromC == LongOrShort.class) return 20008;
+            else if (fromC == ShortOrByte.class) return 20008;
+            else if (fromC == FloatOrInteger.class) return 20008;
+            else if (fromC == FloatOrByte.class) return 20008;
+            else if (fromC == FloatOrShort.class) return 20008;
+            else if (fromC == BigIntegerOrInteger.class) return 10008;
+            else if (fromC == BigIntegerOrLong.class) return 10008;
+            else if (fromC == BigIntegerOrDouble.class) return 10008;
+            else if (fromC == BigIntegerOrFloat.class) return 10008;
+            else if (fromC == BigIntegerOrByte.class) return 10008;
+            else if (fromC == IntegerOrShort.class) return 20008;
+            else if (fromC == DoubleOrShort.class) return 20008;
+            else if (fromC == BigIntegerOrShort.class) return 10008;
+            else return Integer.MAX_VALUE;
+        } else if (toC == BigInteger.class) {
+            if (fromC == Integer.class) return 10005;
+            else if (fromC == IntegerBigDecimal.class) return 10005;
+            else if (fromC == BigDecimal.class) return 40005;
+            else if (fromC == Long.class) return 10005;
+            else if (fromC == Double.class) return Integer.MAX_VALUE;
+            else if (fromC == Float.class) return Integer.MAX_VALUE;
+            else if (fromC == Byte.class) return 10005;
+            else if (fromC == LongOrInteger.class) return 10005;
+            else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+            else if (fromC == DoubleOrIntegerOrFloat.class) return 21005;
+            else if (fromC == DoubleOrInteger.class) return 21005;
+            else if (fromC == DoubleOrLong.class) return 21005;
+            else if (fromC == IntegerOrByte.class) return 10005;
+            else if (fromC == DoubleOrByte.class) return 21005;
+            else if (fromC == LongOrByte.class) return 10005;
+            else if (fromC == Short.class) return 10005;
+            else if (fromC == LongOrShort.class) return 10005;
+            else if (fromC == ShortOrByte.class) return 10005;
+            else if (fromC == FloatOrInteger.class) return 25005;
+            else if (fromC == FloatOrByte.class) return 25005;
+            else if (fromC == FloatOrShort.class) return 25005;
+            else if (fromC == BigIntegerOrInteger.class) return 0;
+            else if (fromC == BigIntegerOrLong.class) return 0;
+            else if (fromC == BigIntegerOrDouble.class) return 0;
+            else if (fromC == BigIntegerOrFloat.class) return 0;
+            else if (fromC == BigIntegerOrByte.class) return 0;
+            else if (fromC == IntegerOrShort.class) return 10005;
+            else if (fromC == DoubleOrShort.class) return 21005;
+            else if (fromC == BigIntegerOrShort.class) return 0;
+            else return Integer.MAX_VALUE;
+        } else {
+            // Unknown toC; we don't know how to convert to it:
+            return Integer.MAX_VALUE;
+        }        
+    }
+
+    static int compareNumberTypeSpecificity(Class c1, Class c2) {
+        // DO NOT EDIT, generated code!
+        // See: src\main\misc\overloadedNumberRules\README.txt
+        c1 = _ClassUtil.primitiveClassToBoxingClass(c1);
+        c2 = _ClassUtil.primitiveClassToBoxingClass(c2);
+        
+        if (c1 == c2) return 0;
+        
+        if (c1 == Integer.class) {
+            if (c2 == Long.class) return 4 - 3;
+            if (c2 == Double.class) return 7 - 3;
+            if (c2 == Float.class) return 6 - 3;
+            if (c2 == Byte.class) return 1 - 3;
+            if (c2 == Short.class) return 2 - 3;
+            if (c2 == BigDecimal.class) return 8 - 3;
+            if (c2 == BigInteger.class) return 5 - 3;
+            return 0;
+        }
+        if (c1 == Long.class) {
+            if (c2 == Integer.class) return 3 - 4;
+            if (c2 == Double.class) return 7 - 4;
+            if (c2 == Float.class) return 6 - 4;
+            if (c2 == Byte.class) return 1 - 4;
+            if (c2 == Short.class) return 2 - 4;
+            if (c2 == BigDecimal.class) return 8 - 4;
+            if (c2 == BigInteger.class) return 5 - 4;
+            return 0;
+        }
+        if (c1 == Double.class) {
+            if (c2 == Integer.class) return 3 - 7;
+            if (c2 == Long.class) return 4 - 7;
+            if (c2 == Float.class) return 6 - 7;
+            if (c2 == Byte.class) return 1 - 7;
+            if (c2 == Short.class) return 2 - 7;
+            if (c2 == BigDecimal.class) return 8 - 7;
+            if (c2 == BigInteger.class) return 5 - 7;
+            return 0;
+        }
+        if (c1 == Float.class) {
+            if (c2 == Integer.class) return 3 - 6;
+            if (c2 == Long.class) return 4 - 6;
+            if (c2 == Double.class) return 7 - 6;
+            if (c2 == Byte.class) return 1 - 6;
+            if (c2 == Short.class) return 2 - 6;
+            if (c2 == BigDecimal.class) return 8 - 6;
+            if (c2 == BigInteger.class) return 5 - 6;
+            return 0;
+        }
+        if (c1 == Byte.class) {
+            if (c2 == Integer.class) return 3 - 1;
+            if (c2 == Long.class) return 4 - 1;
+            if (c2 == Double.class) return 7 - 1;
+            if (c2 == Float.class) return 6 - 1;
+            if (c2 == Short.class) return 2 - 1;
+            if (c2 == BigDecimal.class) return 8 - 1;
+            if (c2 == BigInteger.class) return 5 - 1;
+            return 0;
+        }
+        if (c1 == Short.class) {
+            if (c2 == Integer.class) return 3 - 2;
+            if (c2 == Long.class) return 4 - 2;
+            if (c2 == Double.class) return 7 - 2;
+            if (c2 == Float.class) return 6 - 2;
+            if (c2 == Byte.class) return 1 - 2;
+            if (c2 == BigDecimal.class) return 8 - 2;
+            if (c2 == BigInteger.class) return 5 - 2;
+            return 0;
+        }
+        if (c1 == BigDecimal.class) {
+            if (c2 == Integer.class) return 3 - 8;
+            if (c2 == Long.class) return 4 - 8;
+            if (c2 == Double.class) return 7 - 8;
+            if (c2 == Float.class) return 6 - 8;
+            if (c2 == Byte.class) return 1 - 8;
+            if (c2 == Short.class) return 2 - 8;
+            if (c2 == BigInteger.class) return 5 - 8;
+            return 0;
+        }
+        if (c1 == BigInteger.class) {
+            if (c2 == Integer.class) return 3 - 5;
+            if (c2 == Long.class) return 4 - 5;
+            if (c2 == Double.class) return 7 - 5;
+            if (c2 == Float.class) return 6 - 5;
+            if (c2 == Byte.class) return 1 - 5;
+            if (c2 == Short.class) return 2 - 5;
+            if (c2 == BigDecimal.class) return 8 - 5;
+            return 0;
+        }
+        return 0;
+    }
+
+}


[12/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperConfiguration.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperConfiguration.java
deleted file mode 100644
index 94997cb..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperConfiguration.java
+++ /dev/null
@@ -1,232 +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.beans;
-
-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;
-import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
-
-/**
- * Holds {@link BeansWrapper} configuration settings and defines their defaults.
- * You will not use this abstract class directly, but concrete subclasses like {@link BeansWrapperBuilder} and
- * {@link DefaultObjectWrapperBuilder}. Unless, you are developing a builder for a custom {@link BeansWrapper} subclass.
- * 
- * <p>This class is designed so that its instances can be used as lookup keys in a singleton cache. This is also why
- * this class defines the configuration setting defaults for {@link BeansWrapper}, instead of leaving that to
- * {@link BeansWrapper} itself. (Because, the default values influence the lookup key, and the singleton needs to be
- * looked up without creating a {@link BeansWrapper} instance.) However, because instances are mutable, you should
- * deep-clone it with {@link #clone(boolean)} before using it as cache key.
- * 
- * @since 2.3.21
- */
-public abstract class BeansWrapperConfiguration implements Cloneable {
-
-    private final Version incompatibleImprovements;
-    
-    protected ClassIntrospectorBuilder classIntrospectorFactory;
-    
-    // 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 BeansWrapper#BeansWrapper(Version)}. Not {@code null}. Note
-     *            that the version will be normalized to the lowest version where the same incompatible
-     *            {@link BeansWrapper} 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 BeansWrapper} needs to add
-     *            additional breaking versions over those of {@link BeansWrapper}. Thus, if this parameter is
-     *            {@code true}, the versions where {@link BeansWrapper} had breaking changes must be already factored
-     *            into the {@code incompatibleImprovements} parameter value, as no more normalization will happen. (You
-     *            can use {@link BeansWrapper#normalizeIncompatibleImprovementsVersion(Version)} to discover those.)
-     * 
-     * @since 2.3.22
-     */
-    protected BeansWrapperConfiguration(Version incompatibleImprovements, boolean isIncompImprsAlreadyNormalized) {
-        _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
-        
-        incompatibleImprovements = isIncompImprsAlreadyNormalized
-                ? incompatibleImprovements
-                : BeansWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
-        this.incompatibleImprovements = incompatibleImprovements;
-        
-        classIntrospectorFactory = new ClassIntrospectorBuilder(incompatibleImprovements);
-    }
-    
-    /**
-     * Same as {@link #BeansWrapperConfiguration(Version, boolean) BeansWrapperConfiguration(Version, false)}.
-     */
-    protected BeansWrapperConfiguration(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 + classIntrospectorFactory.hashCode();
-        return result;
-    }
-
-    /**
-     * Two {@link BeansWrapperConfiguration}-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;
-        BeansWrapperConfiguration other = (BeansWrapperConfiguration) 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 classIntrospectorFactory.equals(other.classIntrospectorFactory);
-    }
-    
-    protected Object clone(boolean deepCloneKey) {
-        try {
-            BeansWrapperConfiguration clone = (BeansWrapperConfiguration) super.clone();
-            if (deepCloneKey) {
-                clone.classIntrospectorFactory
-                        = (ClassIntrospectorBuilder) classIntrospectorFactory.clone();
-            }
-            return clone;
-        } catch (CloneNotSupportedException e) {
-            throw new RuntimeException("Failed to clone BeansWrapperConfiguration", e);
-        }
-    }
-    
-    public boolean isSimpleMapWrapper() {
-        return simpleMapWrapper;
-    }
-
-    /** See {@link BeansWrapper#setSimpleMapWrapper(boolean)}. */
-    public void setSimpleMapWrapper(boolean simpleMapWrapper) {
-        this.simpleMapWrapper = simpleMapWrapper;
-    }
-
-    public int getDefaultDateType() {
-        return defaultDateType;
-    }
-
-    /** See {@link BeansWrapper#setDefaultDateType(int)}. */
-    public void setDefaultDateType(int defaultDateType) {
-        this.defaultDateType = defaultDateType;
-    }
-
-    public ObjectWrapper getOuterIdentity() {
-        return outerIdentity;
-    }
-
-    /**
-     * See {@link BeansWrapper#setOuterIdentity(ObjectWrapper)}, except here the default is {@code null} that means
-     * the {@link ObjectWrapper} that you will set up with this {@link BeansWrapperBuilder} object.
-     */
-    public void setOuterIdentity(ObjectWrapper outerIdentity) {
-        this.outerIdentity = outerIdentity;
-    }
-
-    public boolean isStrict() {
-        return strict;
-    }
-
-    /** See {@link BeansWrapper#setStrict(boolean)}. */
-    public void setStrict(boolean strict) {
-        this.strict = strict;
-    }
-
-    public boolean getUseModelCache() {
-        return useModelCache;
-    }
-
-    /** See {@link BeansWrapper#setUseModelCache(boolean)} (it means the same). */
-    public void setUseModelCache(boolean useModelCache) {
-        this.useModelCache = useModelCache;
-    }
-
-    public Version getIncompatibleImprovements() {
-        return incompatibleImprovements;
-    }
-    
-    public int getExposureLevel() {
-        return classIntrospectorFactory.getExposureLevel();
-    }
-
-    /** See {@link BeansWrapper#setExposureLevel(int)}. */
-    public void setExposureLevel(int exposureLevel) {
-        classIntrospectorFactory.setExposureLevel(exposureLevel);
-    }
-
-    public boolean getExposeFields() {
-        return classIntrospectorFactory.getExposeFields();
-    }
-
-    /** See {@link BeansWrapper#setExposeFields(boolean)}. */
-    public void setExposeFields(boolean exposeFields) {
-        classIntrospectorFactory.setExposeFields(exposeFields);
-    }
-
-    public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
-        return classIntrospectorFactory.getMethodAppearanceFineTuner();
-    }
-
-    /**
-     * See {@link BeansWrapper#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) {
-        classIntrospectorFactory.setMethodAppearanceFineTuner(methodAppearanceFineTuner);
-    }
-
-    MethodSorter getMethodSorter() {
-        return classIntrospectorFactory.getMethodSorter();
-    }
-
-    void setMethodSorter(MethodSorter methodSorter) {
-        classIntrospectorFactory.setMethodSorter(methodSorter);
-    }
- 
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BooleanModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BooleanModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BooleanModel.java
deleted file mode 100644
index 857df36..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BooleanModel.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.beans;
-
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-
-/**
- * <p>A class that will wrap instances of {@link java.lang.Boolean} into a
- * {@link TemplateBooleanModel}.
- */
-public class BooleanModel extends BeanModel implements TemplateBooleanModel {
-    private final boolean value;
-    
-    public BooleanModel(Boolean bool, BeansWrapper wrapper) {
-        super(bool, wrapper, false);
-        value = bool.booleanValue();
-    }
-
-    @Override
-    public boolean getAsBoolean() {
-        return value;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/CallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/CallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/CallableMemberDescriptor.java
deleted file mode 100644
index fb55571..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/CallableMemberDescriptor.java
+++ /dev/null
@@ -1,56 +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.beans;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- * Packs a {@link Method} or {@link Constructor} together with its parameter types. The actual
- * {@link Method} or {@link Constructor} is not exposed by the API, because in rare cases calling them require
- * type conversion that the Java reflection API can't do, hence the developer shouldn't be tempted to call them
- * directly. 
- */
-abstract class CallableMemberDescriptor extends MaybeEmptyCallableMemberDescriptor {
-
-    abstract TemplateModel invokeMethod(BeansWrapper bw, Object obj, Object[] args)
-            throws TemplateModelException, InvocationTargetException, IllegalAccessException;
-
-    abstract Object invokeConstructor(BeansWrapper bw, Object[] args)
-            throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
-            TemplateModelException;
-    
-    abstract String getDeclaration();
-    
-    abstract boolean isConstructor();
-    
-    abstract boolean isStatic();
-
-    abstract boolean isVarargs();
-
-    abstract Class[] getParamTypes();
-
-    abstract String getName();
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/CharacterOrString.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/CharacterOrString.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/CharacterOrString.java
deleted file mode 100644
index 4bd297b..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/CharacterOrString.java
+++ /dev/null
@@ -1,45 +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.beans;
-
-import org.apache.freemarker.core.model.TemplateScalarModel;
-
-/**
- * Represents value unwrapped both to {@link Character} and {@link String}. This is needed for unwrapped overloaded
- * method parameters where both {@link Character} and {@link String} occurs on the same parameter position when the
- * {@link TemplateScalarModel} to unwrapp contains a {@link String} of length 1.
- */
-final class CharacterOrString {
-
-    private final String stringValue;
-
-    CharacterOrString(String stringValue) {
-        this.stringValue = stringValue;
-    }
-    
-    String getAsString() {
-        return stringValue;
-    }
-
-    char getAsChar() {
-        return stringValue.charAt(0);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassBasedModelFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassBasedModelFactory.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassBasedModelFactory.java
deleted file mode 100644
index 1fd4d61..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassBasedModelFactory.java
+++ /dev/null
@@ -1,148 +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.beans;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-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.util._ClassUtil;
-
-/**
- * Base class for hash models keyed by Java class names. 
- */
-abstract class ClassBasedModelFactory implements TemplateHashModel {
-    private final BeansWrapper wrapper;
-    
-    private final Map/*<String,TemplateModel>*/ cache = new ConcurrentHashMap();
-    private final Set classIntrospectionsInProgress = new HashSet();
-    
-    protected ClassBasedModelFactory(BeansWrapper wrapper) {
-        this.wrapper = wrapper;
-    }
-
-    @Override
-    public TemplateModel get(String key) throws TemplateModelException {
-        try {
-            return getInternal(key);
-        } catch (Exception e) {
-            if (e instanceof TemplateModelException) {
-                throw (TemplateModelException) e;
-            } else {
-                throw new TemplateModelException(e);
-            }
-        }
-    }
-
-    private TemplateModel getInternal(String key) throws TemplateModelException, ClassNotFoundException {
-        {
-            TemplateModel model = (TemplateModel) cache.get(key);
-            if (model != null) return model;
-        }
-
-        final ClassIntrospector classIntrospector;
-        int classIntrospectorClearingCounter;
-        final Object sharedLock = wrapper.getSharedIntrospectionLock();
-        synchronized (sharedLock) {
-            TemplateModel model = (TemplateModel) cache.get(key);
-            if (model != null) return model;
-            
-            while (model == null
-                    && classIntrospectionsInProgress.contains(key)) {
-                // Another thread is already introspecting this class;
-                // waiting for its result.
-                try {
-                    sharedLock.wait();
-                    model = (TemplateModel) cache.get(key);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(
-                            "Class inrospection data lookup aborded: " + e);
-                }
-            }
-            if (model != null) return model;
-            
-            // This will be the thread that introspects this class.
-            classIntrospectionsInProgress.add(key);
-
-            // While the classIntrospector should not be changed from another thread, badly written apps can do that,
-            // and it's cheap to get the classIntrospector from inside the lock here:   
-            classIntrospector = wrapper.getClassIntrospector();
-            classIntrospectorClearingCounter = classIntrospector.getClearingCounter();
-        }
-        try {
-            final Class clazz = _ClassUtil.forName(key);
-            
-            // This is called so that we trigger the
-            // class-reloading detector. If clazz is a reloaded class,
-            // the wrapper will in turn call our clearCache method.
-            // TODO: Why do we check it now and only now?
-            classIntrospector.get(clazz);
-            
-            TemplateModel model = createModel(clazz);
-            // Warning: model will be null if the class is not good for the subclass.
-            // For example, EnumModels#createModel returns null if clazz is not an enum.
-            
-            if (model != null) {
-                synchronized (sharedLock) {
-                    // Save it into the cache, but only if nothing relevant has changed while we were outside the lock: 
-                    if (classIntrospector == wrapper.getClassIntrospector()
-                            && classIntrospectorClearingCounter == classIntrospector.getClearingCounter()) {  
-                        cache.put(key, model);
-                    }
-                }
-            }
-            return model;
-        } finally {
-            synchronized (sharedLock) {
-                classIntrospectionsInProgress.remove(key);
-                sharedLock.notifyAll();
-            }
-        }
-    }
-    
-    void clearCache() {
-        synchronized (wrapper.getSharedIntrospectionLock()) {
-            cache.clear();
-        }
-    }
-    
-    void removeFromCache(Class clazz) {
-        synchronized (wrapper.getSharedIntrospectionLock()) {
-            cache.remove(clazz.getName());
-        }
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return false;
-    }
-    
-    protected abstract TemplateModel createModel(Class clazz) 
-    throws TemplateModelException;
-    
-    protected BeansWrapper getWrapper() {
-        return wrapper;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassChangeNotifier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassChangeNotifier.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassChangeNotifier.java
deleted file mode 100644
index f85fafd..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassChangeNotifier.java
+++ /dev/null
@@ -1,32 +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.beans;
-
-/**
- * Reports when the non-private interface of a class was changed to the subscribers.   
- */
-interface ClassChangeNotifier {
-    
-    /**
-     * @param classIntrospector Should only be weak-referenced from the monitor object.
-     */
-    void subscribe(ClassIntrospector classIntrospector);
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospector.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospector.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospector.java
deleted file mode 100644
index d1cf191..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospector.java
+++ /dev/null
@@ -1,808 +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.beans;
-
-import java.beans.BeanInfo;
-import java.beans.IndexedPropertyDescriptor;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.MethodDescriptor;
-import java.beans.PropertyDescriptor;
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.freemarker.core._CoreLogs;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecision;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecisionInput;
-import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util._NullArgumentException;
-import org.slf4j.Logger;
-
-/**
- * Returns information about a {@link Class} that's useful for FreeMarker. Encapsulates a cache for this. Thread-safe,
- * doesn't even require "proper publishing" starting from 2.3.24 or Java 5. Immutable, with the exception of the
- * internal caches.
- * 
- * <p>
- * Note that instances of this are cached on the level of FreeMarker's defining class loader. Hence, it must not do
- * operations that depend on the Thread Context Class Loader, such as resolving class names.
- */
-class ClassIntrospector {
-
-    // Attention: This class must be thread-safe (not just after proper publishing). This is important as some of
-    // these are shared by many object wrappers, and concurrency related glitches due to user errors must remain
-    // local to the object wrappers, not corrupting the shared ClassIntrospector.
-
-    private static final Logger LOG = _CoreLogs.BEANS_WRAPPER;
-
-    private static final String JREBEL_SDK_CLASS_NAME = "org.zeroturnaround.javarebel.ClassEventListener";
-    private static final String JREBEL_INTEGRATION_ERROR_MSG
-            = "Error initializing JRebel integration. JRebel integration disabled.";
-
-    private static final ClassChangeNotifier CLASS_CHANGE_NOTIFIER;
-    static {
-        boolean jRebelAvailable;
-        try {
-            Class.forName(JREBEL_SDK_CLASS_NAME);
-            jRebelAvailable = true;
-        } catch (Throwable e) {
-            jRebelAvailable = false;
-            try {
-                if (!(e instanceof ClassNotFoundException)) {
-                    LOG.error(JREBEL_INTEGRATION_ERROR_MSG, e);
-                }
-            } catch (Throwable loggingE) {
-                // ignore
-            }
-        }
-
-        ClassChangeNotifier classChangeNotifier;
-        if (jRebelAvailable) {
-            try {
-                classChangeNotifier = (ClassChangeNotifier)
-                        Class.forName("org.apache.freemarker.core.model.impl.beans.JRebelClassChangeNotifier").newInstance();
-            } catch (Throwable e) {
-                classChangeNotifier = null;
-                try {
-                    LOG.error(JREBEL_INTEGRATION_ERROR_MSG, e);
-                } catch (Throwable loggingE) {
-                    // ignore
-                }
-            }
-        } else {
-            classChangeNotifier = null;
-        }
-
-        CLASS_CHANGE_NOTIFIER = classChangeNotifier;
-    }
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Introspection info Map keys:
-
-    /** Key in the class info Map to the Map that maps method to argument type arrays */
-    private static final Object ARG_TYPES_BY_METHOD_KEY = new Object();
-    /** Key in the class info Map to the object that represents the constructors (one or multiple due to overloading) */
-    static final Object CONSTRUCTORS_KEY = new Object();
-    /** Key in the class info Map to the get(String|Object) Method */
-    static final Object GENERIC_GET_KEY = new Object();
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Introspection configuration properties:
-
-    // Note: These all must be *declared* final (or else synchronization is needed everywhere where they are accessed).
-
-    final int exposureLevel;
-    final boolean exposeFields;
-    final MethodAppearanceFineTuner methodAppearanceFineTuner;
-    final MethodSorter methodSorter;
-
-    /** See {@link #getHasSharedInstanceRestrictons()} */
-    final private boolean hasSharedInstanceRestrictons;
-
-    /** See {@link #isShared()} */
-    final private boolean shared;
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // State fields:
-
-    private final Object sharedLock;
-    private final Map<Class<?>, Map<Object, Object>> cache
-            = new ConcurrentHashMap<>(0, 0.75f, 16);
-    private final Set<String> cacheClassNames = new HashSet<>(0);
-    private final Set<Class<?>> classIntrospectionsInProgress = new HashSet<>(0);
-
-    private final List<WeakReference<Object/*ClassBasedModelFactory|ModelCache>*/>> modelFactories
-            = new LinkedList<>();
-    private final ReferenceQueue<Object> modelFactoriesRefQueue = new ReferenceQueue<>();
-
-    private int clearingCounter;
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Instantiation:
-
-    /**
-     * Creates a new instance, that is hence surely not shared (singleton) instance.
-     * 
-     * @param pa
-     *            Stores what the values of the JavaBean properties of the returned instance will be. Not {@code null}.
-     */
-    ClassIntrospector(ClassIntrospectorBuilder pa, Object sharedLock) {
-        this(pa, sharedLock, false, false);
-    }
-
-    /**
-     * @param hasSharedInstanceRestrictons
-     *            {@code true} exactly if we are creating a new instance with {@link ClassIntrospectorBuilder}. 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) {
-        _NullArgumentException.check("sharedLock", sharedLock);
-
-        exposureLevel = builder.getExposureLevel();
-        exposeFields = builder.getExposeFields();
-        methodAppearanceFineTuner = builder.getMethodAppearanceFineTuner();
-        methodSorter = builder.getMethodSorter();
-
-        this.sharedLock = sharedLock;
-
-        this.hasSharedInstanceRestrictons = hasSharedInstanceRestrictons;
-        this.shared = shared;
-
-        if (CLASS_CHANGE_NOTIFIER != null) {
-            CLASS_CHANGE_NOTIFIER.subscribe(this);
-        }
-    }
-
-    /**
-     * 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.
-     */
-    ClassIntrospectorBuilder getPropertyAssignments() {
-        return new ClassIntrospectorBuilder(this);
-    }
-
-    // ------------------------------------------------------------------------------------------------------------------
-    // Introspection:
-
-    /**
-     * Gets the class introspection data from {@link #cache}, automatically creating the cache entry if it's missing.
-     * 
-     * @return A {@link Map} where each key is a property/method/field name (or a special {@link Object} key like
-     *         {@link #CONSTRUCTORS_KEY}), each value is a {@link PropertyDescriptor} or {@link Method} or
-     *         {@link OverloadedMethods} or {@link Field} (but better check the source code...).
-     */
-    Map<Object, Object> get(Class<?> clazz) {
-        {
-            Map<Object, Object> introspData = cache.get(clazz);
-            if (introspData != null) return introspData;
-        }
-
-        String className;
-        synchronized (sharedLock) {
-            Map<Object, Object> introspData = cache.get(clazz);
-            if (introspData != null) return introspData;
-
-            className = clazz.getName();
-            if (cacheClassNames.contains(className)) {
-                onSameNameClassesDetected(className);
-            }
-
-            while (introspData == null && classIntrospectionsInProgress.contains(clazz)) {
-                // Another thread is already introspecting this class;
-                // waiting for its result.
-                try {
-                    sharedLock.wait();
-                    introspData = cache.get(clazz);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(
-                            "Class inrospection data lookup aborded: " + e);
-                }
-            }
-            if (introspData != null) return introspData;
-
-            // This will be the thread that introspects this class.
-            classIntrospectionsInProgress.add(clazz);
-        }
-        try {
-            Map<Object, Object> introspData = createClassIntrospectionData(clazz);
-            synchronized (sharedLock) {
-                cache.put(clazz, introspData);
-                cacheClassNames.add(className);
-            }
-            return introspData;
-        } finally {
-            synchronized (sharedLock) {
-                classIntrospectionsInProgress.remove(clazz);
-                sharedLock.notifyAll();
-            }
-        }
-    }
-
-    /**
-     * Creates a {@link Map} with the content as described for the return value of {@link #get(Class)}.
-     */
-    private Map<Object, Object> createClassIntrospectionData(Class<?> clazz) {
-        final Map<Object, Object> introspData = new HashMap<>();
-
-        if (exposeFields) {
-            addFieldsToClassIntrospectionData(introspData, clazz);
-        }
-
-        final Map<MethodSignature, List<Method>> accessibleMethods = discoverAccessibleMethods(clazz);
-
-        addGenericGetToClassIntrospectionData(introspData, accessibleMethods);
-
-        if (exposureLevel != BeansWrapper.EXPOSE_NOTHING) {
-            try {
-                addBeanInfoToClassIntrospectionData(introspData, clazz, accessibleMethods);
-            } catch (IntrospectionException e) {
-                LOG.warn("Couldn't properly perform introspection for class {}", clazz.getName(), e);
-                introspData.clear(); // FIXME NBC: Don't drop everything here.
-            }
-        }
-
-        addConstructorsToClassIntrospectionData(introspData, clazz);
-
-        if (introspData.size() > 1) {
-            return introspData;
-        } else if (introspData.size() == 0) {
-            return Collections.emptyMap();
-        } else { // map.size() == 1
-            Entry<Object, Object> e = introspData.entrySet().iterator().next();
-            return Collections.singletonMap(e.getKey(), e.getValue());
-        }
-    }
-
-    private void addFieldsToClassIntrospectionData(Map<Object, Object> introspData, Class<?> clazz)
-            throws SecurityException {
-        for (Field field : clazz.getFields()) {
-            if ((field.getModifiers() & Modifier.STATIC) == 0) {
-                introspData.put(field.getName(), field);
-            }
-        }
-    }
-
-    private void addBeanInfoToClassIntrospectionData(
-            Map<Object, Object> introspData, Class<?> clazz, Map<MethodSignature, List<Method>> accessibleMethods)
-            throws IntrospectionException {
-        BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
-
-        PropertyDescriptor[] pda = beanInfo.getPropertyDescriptors();
-        if (pda != null) {
-            int pdaLength = pda.length;
-            for (int i = pdaLength - 1; i >= 0; --i) {
-                addPropertyDescriptorToClassIntrospectionData(
-                        introspData, pda[i], clazz,
-                        accessibleMethods);
-            }
-        }
-
-        if (exposureLevel < BeansWrapper.EXPOSE_PROPERTIES_ONLY) {
-            final MethodAppearanceDecision decision = new MethodAppearanceDecision();
-            MethodAppearanceDecisionInput decisionInput = null;
-            final MethodDescriptor[] mda = sortMethodDescriptors(beanInfo.getMethodDescriptors());
-            if (mda != null) {
-                int mdaLength = mda.length;
-                for (int i = mdaLength - 1; i >= 0; --i) {
-                    final MethodDescriptor md = mda[i];
-                    final Method method = getMatchingAccessibleMethod(md.getMethod(), accessibleMethods);
-                    if (method != null && isAllowedToExpose(method)) {
-                        decision.setDefaults(method);
-                        if (methodAppearanceFineTuner != null) {
-                            if (decisionInput == null) {
-                                decisionInput = new MethodAppearanceDecisionInput();
-                            }
-                            decisionInput.setContainingClass(clazz);
-                            decisionInput.setMethod(method);
-    
-                            methodAppearanceFineTuner.process(decisionInput, decision);
-                        }
-    
-                        PropertyDescriptor propDesc = decision.getExposeAsProperty();
-                        if (propDesc != null && !(introspData.get(propDesc.getName()) instanceof PropertyDescriptor)) {
-                            addPropertyDescriptorToClassIntrospectionData(
-                                    introspData, propDesc, clazz, accessibleMethods);
-                        }
-    
-                        String methodKey = decision.getExposeMethodAs();
-                        if (methodKey != null) {
-                            Object previous = introspData.get(methodKey);
-                            if (previous instanceof Method) {
-                                // Overloaded method - replace Method with a OverloadedMethods
-                                OverloadedMethods overloadedMethods = new OverloadedMethods();
-                                overloadedMethods.addMethod((Method) previous);
-                                overloadedMethods.addMethod(method);
-                                introspData.put(methodKey, overloadedMethods);
-                                // Remove parameter type information
-                                getArgTypesByMethod(introspData).remove(previous);
-                            } else if (previous instanceof OverloadedMethods) {
-                                // Already overloaded method - add new overload
-                                ((OverloadedMethods) previous).addMethod(method);
-                            } else if (decision.getMethodShadowsProperty()
-                                    || !(previous instanceof PropertyDescriptor)) {
-                                // Simple method (this far)
-                                introspData.put(methodKey, method);
-                                getArgTypesByMethod(introspData).put(method,
-                                        method.getParameterTypes());
-                            }
-                        }
-                    }
-                } // for each in mda
-            } // if mda != null
-        } // end if (exposureLevel < EXPOSE_PROPERTIES_ONLY)
-    }
-
-    private void addPropertyDescriptorToClassIntrospectionData(Map<Object, Object> introspData,
-            PropertyDescriptor pd, Class<?> clazz, Map<MethodSignature, List<Method>> accessibleMethods) {
-        if (pd instanceof IndexedPropertyDescriptor) {
-            IndexedPropertyDescriptor ipd =
-                    (IndexedPropertyDescriptor) pd;
-            Method readMethod = ipd.getIndexedReadMethod();
-            Method publicReadMethod = getMatchingAccessibleMethod(readMethod, accessibleMethods);
-            if (publicReadMethod != null && isAllowedToExpose(publicReadMethod)) {
-                try {
-                    if (readMethod != publicReadMethod) {
-                        ipd = new IndexedPropertyDescriptor(
-                                ipd.getName(), ipd.getReadMethod(),
-                                null, publicReadMethod,
-                                null);
-                    }
-                    introspData.put(ipd.getName(), ipd);
-                    getArgTypesByMethod(introspData).put(publicReadMethod, publicReadMethod.getParameterTypes());
-                } catch (IntrospectionException e) {
-                    LOG.warn("Failed creating a publicly-accessible property descriptor "
-                            + "for {} indexed property {}, read method {}",
-                            clazz.getName(), pd.getName(), publicReadMethod,
-                            e);
-                }
-            }
-        } else {
-            Method readMethod = pd.getReadMethod();
-            Method publicReadMethod = getMatchingAccessibleMethod(readMethod, accessibleMethods);
-            if (publicReadMethod != null && isAllowedToExpose(publicReadMethod)) {
-                try {
-                    if (readMethod != publicReadMethod) {
-                        pd = new PropertyDescriptor(pd.getName(), publicReadMethod, null);
-                        pd.setReadMethod(publicReadMethod);
-                    }
-                    introspData.put(pd.getName(), pd);
-                } catch (IntrospectionException e) {
-                    LOG.warn("Failed creating a publicly-accessible property descriptor "
-                            + "for {} property {}, read method {}",
-                            clazz.getName(), pd.getName(), publicReadMethod,
-                            e);
-                }
-            }
-        }
-    }
-
-    private void addGenericGetToClassIntrospectionData(Map<Object, Object> introspData,
-            Map<MethodSignature, List<Method>> accessibleMethods) {
-        Method genericGet = getFirstAccessibleMethod(
-                MethodSignature.GET_STRING_SIGNATURE, accessibleMethods);
-        if (genericGet == null) {
-            genericGet = getFirstAccessibleMethod(
-                    MethodSignature.GET_OBJECT_SIGNATURE, accessibleMethods);
-        }
-        if (genericGet != null) {
-            introspData.put(GENERIC_GET_KEY, genericGet);
-        }
-    }
-
-    private void addConstructorsToClassIntrospectionData(final Map<Object, Object> introspData,
-            Class<?> clazz) {
-        try {
-            Constructor<?>[] ctors = clazz.getConstructors();
-            if (ctors.length == 1) {
-                Constructor<?> ctor = ctors[0];
-                introspData.put(CONSTRUCTORS_KEY, new SimpleMethod(ctor, ctor.getParameterTypes()));
-            } else if (ctors.length > 1) {
-                OverloadedMethods overloadedCtors = new OverloadedMethods();
-                for (Constructor<?> ctor : ctors) {
-                    overloadedCtors.addConstructor(ctor);
-                }
-                introspData.put(CONSTRUCTORS_KEY, overloadedCtors);
-            }
-        } catch (SecurityException e) {
-            LOG.warn("Can't discover constructors for class {}", clazz.getName(), e);
-        }
-    }
-
-    /**
-     * Retrieves mapping of {@link MethodSignature}-s to a {@link List} of accessible methods for a class. In case the
-     * class is not public, retrieves methods with same signature as its public methods from public superclasses and
-     * interfaces. Basically upcasts every method to the nearest accessible method.
-     */
-    private static Map<MethodSignature, List<Method>> discoverAccessibleMethods(Class<?> clazz) {
-        Map<MethodSignature, List<Method>> accessibles = new HashMap<>();
-        discoverAccessibleMethods(clazz, accessibles);
-        return accessibles;
-    }
-
-    private static void discoverAccessibleMethods(Class<?> clazz, Map<MethodSignature, List<Method>> accessibles) {
-        if (Modifier.isPublic(clazz.getModifiers())) {
-            try {
-                Method[] methods = clazz.getMethods();
-                for (Method method : methods) {
-                    MethodSignature sig = new MethodSignature(method);
-                    // Contrary to intuition, a class can actually have several
-                    // different methods with same signature *but* different
-                    // return types. These can't be constructed using Java the
-                    // language, as this is illegal on source code level, but
-                    // the compiler can emit synthetic methods as part of
-                    // generic type reification that will have same signature
-                    // yet different return type than an existing explicitly
-                    // declared method. Consider:
-                    // public interface I<T> { T m(); }
-                    // public class C implements I<Integer> { Integer m() { return 42; } }
-                    // C.class will have both "Object m()" and "Integer m()" methods.
-                    List<Method> methodList = accessibles.get(sig);
-                    if (methodList == null) {
-                        // TODO Collection.singletonList is more efficient, though read only.
-                        methodList = new LinkedList<>();
-                        accessibles.put(sig, methodList);
-                    }
-                    methodList.add(method);
-                }
-                return;
-            } catch (SecurityException e) {
-                LOG.warn("Could not discover accessible methods of class {}, attemping superclasses/interfaces.",
-                        clazz.getName(), e);
-                // Fall through and attempt to discover superclass/interface methods
-            }
-        }
-
-        Class<?>[] interfaces = clazz.getInterfaces();
-        for (Class<?> anInterface : interfaces) {
-            discoverAccessibleMethods(anInterface, accessibles);
-        }
-        Class<?> superclass = clazz.getSuperclass();
-        if (superclass != null) {
-            discoverAccessibleMethods(superclass, accessibles);
-        }
-    }
-
-    private static Method getMatchingAccessibleMethod(Method m, Map<MethodSignature, List<Method>> accessibles) {
-        if (m == null) {
-            return null;
-        }
-        MethodSignature sig = new MethodSignature(m);
-        List<Method> ams = accessibles.get(sig);
-        if (ams == null) {
-            return null;
-        }
-        for (Method am : ams) {
-            if (am.getReturnType() == m.getReturnType()) {
-                return am;
-            }
-        }
-        return null;
-    }
-
-    private static Method getFirstAccessibleMethod(MethodSignature sig, Map<MethodSignature, List<Method>> accessibles) {
-        List<Method> ams = accessibles.get(sig);
-        if (ams == null || ams.isEmpty()) {
-            return null;
-        }
-        return ams.get(0);
-    }
-
-    /**
-     * As of this writing, this is only used for testing if method order really doesn't mater.
-     */
-    private MethodDescriptor[] sortMethodDescriptors(MethodDescriptor[] methodDescriptors) {
-        return methodSorter != null ? methodSorter.sortMethodDescriptors(methodDescriptors) : methodDescriptors;
-    }
-
-    boolean isAllowedToExpose(Method method) {
-        return exposureLevel < BeansWrapper.EXPOSE_SAFE || !UnsafeMethods.isUnsafeMethod(method);
-    }
-
-    private static Map<Method, Class<?>[]> getArgTypesByMethod(Map<Object, Object> classInfo) {
-        @SuppressWarnings("unchecked")
-        Map<Method, Class<?>[]> argTypes = (Map<Method, Class<?>[]>) classInfo.get(ARG_TYPES_BY_METHOD_KEY);
-        if (argTypes == null) {
-            argTypes = new HashMap<>();
-            classInfo.put(ARG_TYPES_BY_METHOD_KEY, argTypes);
-        }
-        return argTypes;
-    }
-
-    private static final class MethodSignature {
-        private static final MethodSignature GET_STRING_SIGNATURE =
-                new MethodSignature("get", new Class[] { String.class });
-        private static final MethodSignature GET_OBJECT_SIGNATURE =
-                new MethodSignature("get", new Class[] { Object.class });
-
-        private final String name;
-        private final Class<?>[] args;
-
-        private MethodSignature(String name, Class<?>[] args) {
-            this.name = name;
-            this.args = args;
-        }
-
-        MethodSignature(Method method) {
-            this(method.getName(), method.getParameterTypes());
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof MethodSignature) {
-                MethodSignature ms = (MethodSignature) o;
-                return ms.name.equals(name) && Arrays.equals(args, ms.args);
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return name.hashCode() ^ args.length; // TODO That's a poor quality hash... isn't this a problem?
-        }
-    }
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Cache management:
-
-    /**
-     * Corresponds to {@link BeansWrapper#clearClassIntrospecitonCache()}.
-     * 
-     * @since 2.3.20
-     */
-    void clearCache() {
-        if (getHasSharedInstanceRestrictons()) {
-            throw new IllegalStateException(
-                    "It's not allowed to clear the whole cache in a read-only " + getClass().getName() +
-                            "instance. Use removeFromClassIntrospectionCache(String prefix) instead.");
-        }
-        forcedClearCache();
-    }
-
-    private void forcedClearCache() {
-        synchronized (sharedLock) {
-            cache.clear();
-            cacheClassNames.clear();
-            clearingCounter++;
-
-            for (WeakReference<Object> regedMfREf : modelFactories) {
-                Object regedMf = regedMfREf.get();
-                if (regedMf != null) {
-                    if (regedMf instanceof ClassBasedModelFactory) {
-                        ((ClassBasedModelFactory) regedMf).clearCache();
-                    } else if (regedMf instanceof ModelCache) {
-                        ((ModelCache) regedMf).clearCache();
-                    } else {
-                        throw new BugException();
-                    }
-                }
-            }
-
-            removeClearedModelFactoryReferences();
-        }
-    }
-
-    /**
-     * Corresponds to {@link BeansWrapper#removeFromClassIntrospectionCache(Class)}.
-     * 
-     * @since 2.3.20
-     */
-    void remove(Class<?> clazz) {
-        synchronized (sharedLock) {
-            cache.remove(clazz);
-            cacheClassNames.remove(clazz.getName());
-            clearingCounter++;
-
-            for (WeakReference<Object> regedMfREf : modelFactories) {
-                Object regedMf = regedMfREf.get();
-                if (regedMf != null) {
-                    if (regedMf instanceof ClassBasedModelFactory) {
-                        ((ClassBasedModelFactory) regedMf).removeFromCache(clazz);
-                    } else if (regedMf instanceof ModelCache) {
-                        ((ModelCache) regedMf).clearCache(); // doesn't support selective clearing ATM
-                    } else {
-                        throw new BugException();
-                    }
-                }
-            }
-
-            removeClearedModelFactoryReferences();
-        }
-    }
-
-    /**
-     * Returns the number of events so far that could make class introspection data returned earlier outdated.
-     */
-    int getClearingCounter() {
-        synchronized (sharedLock) {
-            return clearingCounter;
-        }
-    }
-
-    private void onSameNameClassesDetected(String className) {
-        // TODO: This behavior should be pluggable, as in environments where
-        // some classes are often reloaded or multiple versions of the
-        // same class is normal (OSGi), this will drop the cache contents
-        // too often.
-        LOG.info(
-                "Detected multiple classes with the same name, \"{}\". "
-                + "Assuming it was a class-reloading. Clearing class introspection caches to release old data.",
-                className);
-        forcedClearCache();
-    }
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Managing dependent objects:
-
-    void registerModelFactory(ClassBasedModelFactory mf) {
-        registerModelFactory((Object) mf);
-    }
-
-    void registerModelFactory(ModelCache mf) {
-        registerModelFactory((Object) mf);
-    }
-
-    private void registerModelFactory(Object mf) {
-        // Note that this `synchronized (sharedLock)` is also need for the BeansWrapper constructor to work safely.
-        synchronized (sharedLock) {
-            modelFactories.add(new WeakReference<>(mf, modelFactoriesRefQueue));
-            removeClearedModelFactoryReferences();
-        }
-    }
-
-    void unregisterModelFactory(ClassBasedModelFactory mf) {
-        unregisterModelFactory((Object) mf);
-    }
-
-    void unregisterModelFactory(ModelCache mf) {
-        unregisterModelFactory((Object) mf);
-    }
-
-    void unregisterModelFactory(Object mf) {
-        synchronized (sharedLock) {
-            for (Iterator<WeakReference<Object>> it = modelFactories.iterator(); it.hasNext(); ) {
-                Object regedMf = it.next().get();
-                if (regedMf == mf) {
-                    it.remove();
-                }
-            }
-
-        }
-    }
-
-    private void removeClearedModelFactoryReferences() {
-        Reference<?> cleardRef;
-        while ((cleardRef = modelFactoriesRefQueue.poll()) != null) {
-            synchronized (sharedLock) {
-                findClearedRef: for (Iterator<WeakReference<Object>> it = modelFactories.iterator(); it.hasNext(); ) {
-                    if (it.next() == cleardRef) {
-                        it.remove();
-                        break findClearedRef;
-                    }
-                }
-            }
-        }
-    }
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Extracting from introspection info:
-
-    static Class<?>[] getArgTypes(Map<Object, Object> classInfo, Method method) {
-        @SuppressWarnings("unchecked")
-        Map<Method, Class<?>[]> argTypesByMethod = (Map<Method, Class<?>[]>) classInfo.get(ARG_TYPES_BY_METHOD_KEY);
-        return argTypesByMethod.get(method);
-    }
-
-    /**
-     * Returns the number of introspected methods/properties that should be available via the TemplateHashModel
-     * interface.
-     */
-    int keyCount(Class<?> clazz) {
-        Map<Object, Object> map = get(clazz);
-        int count = map.size();
-        if (map.containsKey(CONSTRUCTORS_KEY)) count--;
-        if (map.containsKey(GENERIC_GET_KEY)) count--;
-        if (map.containsKey(ARG_TYPES_BY_METHOD_KEY)) count--;
-        return count;
-    }
-
-    /**
-     * Returns the Set of names of introspected methods/properties that should be available via the TemplateHashModel
-     * interface.
-     */
-    Set<Object> keySet(Class<?> clazz) {
-        Set<Object> set = new HashSet<>(get(clazz).keySet());
-        set.remove(CONSTRUCTORS_KEY);
-        set.remove(GENERIC_GET_KEY);
-        set.remove(ARG_TYPES_BY_METHOD_KEY);
-        return set;
-    }
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Properties
-
-    int getExposureLevel() {
-        return exposureLevel;
-    }
-
-    boolean getExposeFields() {
-        return exposeFields;
-    }
-
-    MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
-        return methodAppearanceFineTuner;
-    }
-
-    MethodSorter getMethodSorter() {
-        return methodSorter;
-    }
-
-    /**
-     * Returns {@code true} if this instance was created with {@link ClassIntrospectorBuilder}, even if it wasn't
-     * actually put into the cache (as we reserve the right to do so in later versions).
-     */
-    boolean getHasSharedInstanceRestrictons() {
-        return hasSharedInstanceRestrictons;
-    }
-
-    /**
-     * Tells if this instance is (potentially) shared among {@link BeansWrapper} instances.
-     * 
-     * @see #getHasSharedInstanceRestrictons()
-     */
-    boolean isShared() {
-        return shared;
-    }
-
-    /**
-     * Almost always, you want to use {@link BeansWrapper#getSharedIntrospectionLock()}, not this! The only exception is
-     * when you get this to set the field returned by {@link BeansWrapper#getSharedIntrospectionLock()}.
-     */
-    Object getSharedLock() {
-        return sharedLock;
-    }
-
-    // -----------------------------------------------------------------------------------------------------------------
-    // Monitoring:
-
-    /** For unit testing only */
-    Object[] getRegisteredModelFactoriesSnapshot() {
-        synchronized (sharedLock) {
-            return modelFactories.toArray();
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospectorBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospectorBuilder.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospectorBuilder.java
deleted file mode 100644
index c6f6cb6..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/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.beans;
-
-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 = BeansWrapper.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 BeansWrapper.normalizeIncompatibleImprovements results. That is, this class may don't react
-        // to some version changes that affects BeansWrapper, 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 BeansWrapper#setExposureLevel(int)}. */
-    public void setExposureLevel(int exposureLevel) {
-        if (exposureLevel < BeansWrapper.EXPOSE_ALL || exposureLevel > BeansWrapper.EXPOSE_NOTHING) {
-            throw new IllegalArgumentException("Illegal exposure level: " + exposureLevel);
-        }
-        
-        this.exposureLevel = exposureLevel;
-    }
-
-    public boolean getExposeFields() {
-        return exposeFields;
-    }
-
-    /** See {@link BeansWrapper#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
-            // BeansWrapper.
-            return new ClassIntrospector(this, new Object(), true, false);
-        }
-    }
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionAdapter.java
deleted file mode 100644
index 74db4cb..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionAdapter.java
+++ /dev/null
@@ -1,88 +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.beans;
-
-import java.util.AbstractCollection;
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-
-/**
- * Adapts a {@link TemplateCollectionModel} to  {@link Collection}.
- */
-class CollectionAdapter extends AbstractCollection implements TemplateModelAdapter {
-    private final BeansWrapper wrapper;
-    private final TemplateCollectionModel model;
-    
-    CollectionAdapter(TemplateCollectionModel model, BeansWrapper wrapper) {
-        this.model = model;
-        this.wrapper = wrapper;
-    }
-    
-    @Override
-    public TemplateModel getTemplateModel() {
-        return model;
-    }
-    
-    @Override
-    public int size() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Iterator iterator() {
-        try {
-            return new Iterator() {
-                final TemplateModelIterator i = model.iterator();
-    
-                @Override
-                public boolean hasNext() {
-                    try {
-                        return i.hasNext();
-                    } catch (TemplateModelException e) {
-                        throw new UndeclaredThrowableException(e);
-                    }
-                }
-                
-                @Override
-                public Object next() {
-                    try {
-                        return wrapper.unwrap(i.next());
-                    } catch (TemplateModelException e) {
-                        throw new UndeclaredThrowableException(e);
-                    }
-                }
-                
-                @Override
-                public void remove() {
-                    throw new UnsupportedOperationException();
-                }
-            };
-        } catch (TemplateModelException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionModel.java
deleted file mode 100644
index d27a56d..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionModel.java
+++ /dev/null
@@ -1,109 +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.beans;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-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.TemplateSequenceModel;
-
-/**
- * <p>A special case of {@link BeanModel} that can wrap Java collections
- * and that implements the {@link TemplateCollectionModel} in order to be usable 
- * in a <tt>&lt;#list&gt;</tt> block.</p>
- */
-public class CollectionModel
-extends
-    StringModel
-implements
-    TemplateCollectionModel,
-    TemplateSequenceModel {
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new CollectionModel((Collection) object, (BeansWrapper) wrapper);
-            }
-        };
-
-
-    /**
-     * Creates a new model that wraps the specified collection object.
-     * @param collection the collection object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     */
-    public CollectionModel(Collection collection, BeansWrapper wrapper) {
-        super(collection, wrapper);
-    }
-
-    /**
-     * Retrieves the i-th object from the collection, wrapped as a TemplateModel.
-     * @throws TemplateModelException if the index is out of bounds, or the
-     * underlying collection is not a List.
-     */
-    @Override
-    public TemplateModel get(int index)
-    throws TemplateModelException {
-        // Don't forget to keep getSupportsIndexedAccess in sync with this!
-        if (object instanceof List) {
-            try {
-                return wrap(((List) object).get(index));
-            } catch (IndexOutOfBoundsException e) {
-                return null;
-//                throw new TemplateModelException("Index out of bounds: " + index);
-            }
-        } else {
-            throw new TemplateModelException("Underlying collection is not a list, it's " + object.getClass().getName());
-        }
-    }
-    
-    /**
-     * Tells if {@link #get(int)} will always fail for this object.
-     * As this object implements {@link TemplateSequenceModel},
-     * {@link #get(int)} should always work, but due to a design flaw, for
-     * non-{@link List} wrapped objects {@link #get(int)} will always fail.
-     * This method exists to ease working this problem around.
-     * 
-     * @since 2.3.17 
-     */
-    public boolean getSupportsIndexedAccess() {
-        return object instanceof List;
-    }
-    
-    @Override
-    public TemplateModelIterator iterator() {
-        return new IteratorModel(((Collection) object).iterator(), wrapper);
-    }
-
-    @Override
-    public int size() {
-        return ((Collection) object).size();
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/DateModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/DateModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/DateModel.java
deleted file mode 100644
index 55419cd..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/DateModel.java
+++ /dev/null
@@ -1,79 +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.beans;
-
-import java.util.Date;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateModel;
-
-/**
- * Wraps arbitrary subclass of {@link java.util.Date} into a reflective model.
- * Beside acting as a {@link TemplateDateModel}, you can call all Java methods
- * on these objects as well.
- */
-public class DateModel
-extends
-    BeanModel
-implements
-    TemplateDateModel {
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new DateModel((Date) object, (BeansWrapper) wrapper);
-            }
-        };
-
-    private final int type;
-    
-    /**
-     * Creates a new model that wraps the specified date object.
-     * @param date the date object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     */
-    public DateModel(Date date, BeansWrapper wrapper) {
-        super(date, wrapper);
-        if (date instanceof java.sql.Date) {
-            type = DATE;
-        } else if (date instanceof java.sql.Time) {
-            type = TIME;
-        } else if (date instanceof java.sql.Timestamp) {
-            type = DATETIME;
-        } else {
-            type = wrapper.getDefaultDateType();
-        }
-    }
-
-    @Override
-    public Date getAsDate() {
-        return (Date) object;
-    }
-
-    @Override
-    public int getDateType() {
-        return type;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyCallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyCallableMemberDescriptor.java
deleted file mode 100644
index d0e6b8e..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyCallableMemberDescriptor.java
+++ /dev/null
@@ -1,35 +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.beans;
-
-/**
- * Represents that no member was chosen. Why it wasn't is represented by the two singleton instances,
- * {@link #NO_SUCH_METHOD} and {@link #AMBIGUOUS_METHOD}. (Note that instances of these are cached associated with the
- * argument types, thus it shouldn't store details that are specific to the actual argument values. In fact, it better
- * remains a set of singletons.)     
- */
-final class EmptyCallableMemberDescriptor extends MaybeEmptyCallableMemberDescriptor {
-    
-    static final EmptyCallableMemberDescriptor NO_SUCH_METHOD = new EmptyCallableMemberDescriptor();
-    static final EmptyCallableMemberDescriptor AMBIGUOUS_METHOD = new EmptyCallableMemberDescriptor();
-    
-    private EmptyCallableMemberDescriptor() { }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyMemberAndArguments.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyMemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyMemberAndArguments.java
deleted file mode 100644
index 2e078c9..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyMemberAndArguments.java
+++ /dev/null
@@ -1,93 +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.beans;
-
-import org.apache.freemarker.core._DelayedOrdinal;
-
-/**
- * Describes a failed member lookup. Instances of this must not be cached as instances may store the actual argument
- * values.
- */
-final class EmptyMemberAndArguments extends MaybeEmptyMemberAndArguments {
-    
-    static final EmptyMemberAndArguments WRONG_NUMBER_OF_ARGUMENTS
-            = new EmptyMemberAndArguments(
-                    "No compatible overloaded variation was found; wrong number of arguments.", true, null);
-    
-    private final Object errorDescription;
-    private final boolean numberOfArgumentsWrong;
-    private final Object[] unwrappedArguments;
-    
-    private EmptyMemberAndArguments(
-            Object errorDescription, boolean numberOfArgumentsWrong, Object[] unwrappedArguments) {
-        this.errorDescription = errorDescription;
-        this.numberOfArgumentsWrong = numberOfArgumentsWrong;
-        this.unwrappedArguments = unwrappedArguments;
-    }
-
-    static EmptyMemberAndArguments noCompatibleOverload(int unwrappableIndex) {
-        return new EmptyMemberAndArguments(
-                new Object[] { "No compatible overloaded variation was found; can't convert (unwrap) the ",
-                new _DelayedOrdinal(Integer.valueOf(unwrappableIndex)), " argument to the desired Java type." },
-                false,
-                null);
-    }
-    
-    static EmptyMemberAndArguments noCompatibleOverload(Object[] unwrappedArgs) {
-        return new EmptyMemberAndArguments(
-                "No compatible overloaded variation was found; declared parameter types and argument value types mismatch.",
-                false,
-                unwrappedArgs);
-    }
-
-    static EmptyMemberAndArguments ambiguous(Object[] unwrappedArgs) {
-        return new EmptyMemberAndArguments(
-                "Multiple compatible overloaded variations were found with the same priority.",
-                false,
-                unwrappedArgs);
-    }
-
-    static MaybeEmptyMemberAndArguments from(
-            EmptyCallableMemberDescriptor emtpyMemberDesc, Object[] unwrappedArgs) {
-        if (emtpyMemberDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) {
-            return noCompatibleOverload(unwrappedArgs);
-        } else if (emtpyMemberDesc == EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD) {
-            return ambiguous(unwrappedArgs);
-        } else {
-            throw new IllegalArgumentException("Unrecognized constant: " + emtpyMemberDesc);
-        }
-    }
-
-    Object getErrorDescription() {
-        return errorDescription;
-    }
-
-    /**
-     * @return {@code null} if the error has occurred earlier than the full argument list was unwrapped.
-     */
-    Object[] getUnwrappedArguments() {
-        return unwrappedArguments;
-    }
-
-    public boolean isNumberOfArgumentsWrong() {
-        return numberOfArgumentsWrong;
-    }
-    
-}


[18/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/DefaultEnumerationAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultEnumerationAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultEnumerationAdapter.java
new file mode 100644
index 0000000..ec20b9f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultEnumerationAdapter.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import org.apache.freemarker.core.model.AdapterTemplateModel;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+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.WrapperTemplateModel;
+import org.apache.freemarker.core.model.WrappingTemplateModel;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Adapts an {@link Enumeration} to the corresponding {@link TemplateModel} interface(s), most importantly to
+ * {@link TemplateCollectionModel}. Putting aside that it wraps an {@link Enumeration} instead of an {@link Iterator},
+ * this is identical to {@link DefaultIteratorAdapter}, so see further details there.
+ */
+// TODO JUnit
+public class DefaultEnumerationAdapter extends WrappingTemplateModel implements TemplateCollectionModel,
+        AdapterTemplateModel, WrapperTemplateModel, Serializable {
+
+    @SuppressFBWarnings(value="SE_BAD_FIELD", justification="We hope it's Seralizable")
+    private final Enumeration enumeration;
+    private boolean enumerationOwnedBySomeone;
+
+    /**
+     * Factory method for creating new adapter instances.
+     *
+     * @param iterator
+     *            The enumeration to adapt; can't be {@code null}.
+     */
+    public static DefaultEnumerationAdapter adapt(Enumeration iterator, ObjectWrapper wrapper) {
+        return new DefaultEnumerationAdapter(iterator, wrapper);
+    }
+
+    private DefaultEnumerationAdapter(Enumeration enumeration, ObjectWrapper wrapper) {
+        super(wrapper);
+        this.enumeration = enumeration;
+    }
+
+    @Override
+    public Object getWrappedObject() {
+        return enumeration;
+    }
+
+    @Override
+    public Object getAdaptedObject(Class hint) {
+        return getWrappedObject();
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateModelException {
+        return new SimpleTemplateModelIterator();
+    }
+
+    /**
+     * Not thread-safe.
+     */
+    private class SimpleTemplateModelIterator implements TemplateModelIterator {
+
+        private boolean enumerationOwnedByMe;
+
+        @Override
+        public TemplateModel next() throws TemplateModelException {
+            if (!enumerationOwnedByMe) {
+                checkNotOwner();
+                enumerationOwnedBySomeone = true;
+                enumerationOwnedByMe = true;
+            }
+
+            if (!enumeration.hasMoreElements()) {
+                throw new TemplateModelException("The collection has no more items.");
+            }
+
+            Object value = enumeration.nextElement();
+            return value instanceof TemplateModel ? (TemplateModel) value : wrap(value);
+        }
+
+        @Override
+        public boolean hasNext() throws TemplateModelException {
+            // Calling hasNext may looks safe, but I have met sync. problems.
+            if (!enumerationOwnedByMe) {
+                checkNotOwner();
+            }
+
+            return enumeration.hasMoreElements();
+        }
+
+        private void checkNotOwner() throws TemplateModelException {
+            if (enumerationOwnedBySomeone) {
+                throw new TemplateModelException(
+                        "This collection value wraps a java.util.Enumeration, thus it can be listed only once.");
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 0351edd..ebc5a3d 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,22 +19,52 @@
 
 package org.apache.freemarker.core.model.impl;
 
+import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Array;
-import java.util.ArrayList;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
 
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.Version;
+import org.apache.freemarker.core._CoreAPI;
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core._DelayedFTLTypeDescription;
+import org.apache.freemarker.core._DelayedShortClassName;
+import org.apache.freemarker.core._TemplateModelException;
+import org.apache.freemarker.core.model.AdapterTemplateModel;
 import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.RichObjectWrapper;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
 import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperConfiguration;
+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.WrapperTemplateModel;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util.WriteProtectable;
+import org.apache.freemarker.core.util._ClassUtil;
 import org.apache.freemarker.dom.NodeModel;
+import org.slf4j.Logger;
 import org.w3c.dom.Node;
 
 /**
@@ -54,46 +84,583 @@ import org.w3c.dom.Node;
  * 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.
  */
-public class DefaultObjectWrapper extends org.apache.freemarker.core.model.impl.beans.BeansWrapper {
-    
+// TODO Get rid of unused stuff (including other clases in this package) coming from BeansWrapper.
+public class DefaultObjectWrapper implements RichObjectWrapper, WriteProtectable {
+
+    private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
+
+    /**
+     * At this level of exposure, all methods and properties of the
+     * wrapped objects are exposed to the template.
+     */
+    public static final int EXPOSE_ALL = 0;
+
+    /**
+     * At this level of exposure, all methods and properties of the wrapped
+     * objects are exposed to the template except methods that are deemed
+     * not safe. The not safe methods are java.lang.Object methods wait() and
+     * notify(), java.lang.Class methods getClassLoader() and newInstance(),
+     * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and
+     * newInstance() methods, all java.lang.reflect.Field set methods, all
+     * java.lang.Thread and java.lang.ThreadGroup methods that can change its
+     * state, as well as the usual suspects in java.lang.System and
+     * java.lang.Runtime.
+     */
+    public static final int EXPOSE_SAFE = 1;
+
+    /**
+     * At this level of exposure, only property getters are exposed.
+     * Additionally, property getters that map to unsafe methods are not
+     * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
+     */
+    public static final int EXPOSE_PROPERTIES_ONLY = 2;
+
+    /**
+     * 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.
+     */
+    public static final int EXPOSE_NOTHING = 3;
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Introspection cache:
+
+    private final Object sharedIntrospectionLock;
+
+    /**
+     * {@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;
+
+    /**
+     * {@link String} class name to {@link StaticModel} cache.
+     * This object only belongs to a single {@link DefaultObjectWrapper}.
+     * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good
+     * reference.
+     */
+    private final StaticModels staticModels;
+
     /**
-     * Use {@link DefaultObjectWrapperBuilder} instead if possible. Instances created with this constructor won't share
-     * the class introspection caches with other instances. See {@link BeansWrapper#BeansWrapper(Version)} (the
-     * superclass constructor) for more details.
-     * 
+     * {@link String} class name to {@link EnumerationModel} cache.
+     * This object only belongs to a single {@link DefaultObjectWrapper}.
+     * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good
+     * reference.
+     */
+    private final ClassBasedModelFactory enumModels;
+
+    /**
+     * Object to wrapped object cache; not used by default.
+     * This object only belongs to a single {@link DefaultObjectWrapper}.
+     */
+    private final ModelCache modelCache;
+
+    private final BooleanModel falseModel;
+    private final BooleanModel trueModel;
+
+    // -----------------------------------------------------------------------------------------------------------------
+
+    // 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 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
-     *            It's the same as in {@link BeansWrapper#BeansWrapper(Version)}.
-     * 
+     *   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);
+        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.
     }
 
     /**
-     * Use {@link #DefaultObjectWrapper(DefaultObjectWrapperConfiguration, boolean)} instead if possible;
-     * it does the same, except that it tolerates a non-{@link DefaultObjectWrapperConfiguration} configuration too.
-     * 
+     * Same as {@link #DefaultObjectWrapper(DefaultObjectWrapperConfiguration, boolean, boolean)} with {@code true}
+     * {@code finalizeConstruction} argument.
+     *
      * @since 2.3.21
      */
-    protected DefaultObjectWrapper(BeansWrapperConfiguration bwCfg, boolean writeProtected) {
-        super(bwCfg, writeProtected, false);
-        DefaultObjectWrapperConfiguration dowDowCfg = bwCfg instanceof DefaultObjectWrapperConfiguration
-                ? (DefaultObjectWrapperConfiguration) bwCfg
-                : new DefaultObjectWrapperConfiguration(bwCfg.getIncompatibleImprovements()) { }; 
+    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.
+     *
+     * @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
+     */
+    protected DefaultObjectWrapper(DefaultObjectWrapperConfiguration bwConf, boolean writeProtected, boolean finalizeConstruction) {
+        incompatibleImprovements = bwConf.getIncompatibleImprovements();  // normalized
+
+        defaultDateType = bwConf.getDefaultDateType();
+        outerIdentity = bwConf.getOuterIdentity() != null ? bwConf.getOuterIdentity() : this;
+        strict = bwConf.isStrict();
+
+        if (!writeProtected) {
+            // 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.classIntrospectorFactory, 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.classIntrospectorFactory.build();
+            sharedIntrospectionLock = classIntrospector.getSharedLock();
+        }
+
+        falseModel = new BooleanModel(Boolean.FALSE, this);
+        trueModel = new BooleanModel(Boolean.TRUE, this);
+
+        staticModels = new StaticModels(this);
+        enumModels = new _EnumModels(this);
+        modelCache = new BeansModelCache(this);
+        setUseModelCache(bwConf.getUseModelCache());
+
         finalizeConstruction(writeProtected);
     }
 
     /**
-     * Calls {@link BeansWrapper#BeansWrapper(BeansWrapperConfiguration, boolean)} and sets up
-     * {@link DefaultObjectWrapper}-specific fields.
-     * 
+     * Meant to be called after {@link DefaultObjectWrapper#DefaultObjectWrapper(DefaultObjectWrapperConfiguration, boolean, 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 DefaultObjectWrapper(DefaultObjectWrapperConfiguration dowCfg, boolean writeProtected) {
-        this((BeansWrapperConfiguration) dowCfg, writeProtected);
+    protected void finalizeConstruction(boolean writeProtected) {
+        if (writeProtected) {
+            writeProtect();
+        }
+
+        // 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
+        // classIntrospector.sharedLock, and so is classIntrospector.registerModelFactory(...).
+
+        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)
+     */
+    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)
+     */
+    public ObjectWrapper getOuterIdentity() {
+        return outerIdentity;
+    }
+
+    // I have commented this out, as it won't be in 2.3.20 yet.
+    /*
+    /**
+     * Tells which non-backward-compatible overloaded method selection fixes to apply;
+     * see {@link #setOverloadedMethodSelection(Version)}.
+     * /
+    public Version getOverloadedMethodSelection() {
+        return overloadedMethodSelection;
+    }
+
+    /**
+     * Sets which non-backward-compatible overloaded method selection fixes to apply.
+     * This has similar logic as {@link Configuration#setIncompatibleImprovements(Version)},
+     * but only applies to this aspect.
+     *
+     * Currently significant values:
+     * <ul>
+     *   <li>2.3.21: Completetlly rewritten overloaded method selection, fixes several issues with the old one.</li>
+     * </ul>
+     * /
+    public void setOverloadedMethodSelection(Version version) {
+        overloadedMethodSelection = version;
+    }
+    */
+
+    /**
+     * 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 pa = classIntrospector.getPropertyAssignments();
+            pa.setExposureLevel(exposureLevel);
+            replaceClassIntrospector(pa);
+        }
+    }
+
+    /**
+     * @since 2.3.21
+     */
+    public int getExposureLevel() {
+        return classIntrospector.getExposureLevel();
+    }
+
+    /**
+     * 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 pa = classIntrospector.getPropertyAssignments();
+            pa.setExposeFields(exposeFields);
+            replaceClassIntrospector(pa);
+        }
+    }
+
+    /**
+     * Returns whether exposure of public instance fields of classes is
+     * enabled. See {@link #setExposeFields(boolean)} for details.
+     * @return true if public instance fields are exposed, false otherwise.
+     */
+    public boolean isExposeFields() {
+        return classIntrospector.getExposeFields();
+    }
+
+    public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
+        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 pa = classIntrospector.getPropertyAssignments();
+            pa.setMethodAppearanceFineTuner(methodAppearanceFineTuner);
+            replaceClassIntrospector(pa);
+        }
+    }
+
+    MethodSorter getMethodSorter() {
+        return classIntrospector.getMethodSorter();
+    }
+
+    void setMethodSorter(MethodSorter methodSorter) {
+        checkModifiable();
+
+        if (classIntrospector.getMethodSorter() != methodSorter) {
+            ClassIntrospectorBuilder pa = classIntrospector.getPropertyAssignments();
+            pa.setMethodSorter(methodSorter);
+            replaceClassIntrospector(pa);
+        }
+    }
+
+    /**
+     * 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}
+     * (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.
+     *
+     * @since 2.3.21
+     */
+    public boolean isClassIntrospectionCacheRestricted() {
+        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 pa) {
+        checkModifiable();
+
+        final ClassIntrospector newCI = new ClassIntrospector(pa, 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();
+                }
+                if (modelCache != null) {
+                    oldCI.unregisterModelFactory(modelCache);
+                    modelCache.clearCache();
+                }
+                if (trueModel != null) {
+                    trueModel.clearMemberCache();
+                }
+                if (falseModel != null) {
+                    falseModel.clearMemberCache();
+                }
+            }
+
+            classIntrospector = newCI;
+
+            registerModelFactories();
+        }
+    }
+
+    private void registerModelFactories() {
+        if (staticModels != null) {
+            classIntrospector.registerModelFactory(staticModels);
+        }
+        if (enumModels != null) {
+            classIntrospector.registerModelFactory(enumModels);
+        }
+        if (modelCache != null) {
+            classIntrospector.registerModelFactory(modelCache);
+        }
+    }
+
+    /**
+     * 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
+     * details.
+     * @return the default date type
+     */
+    public int getDefaultDateType() {
+        return defaultDateType;
+    }
+
+    /**
+     * Sets whether this wrapper caches the {@link TemplateModel}-s created for the Java objects that has wrapped with
+     * this object wrapper. Default is {@code false}.
+     * When set to {@code true}, calling {@link #wrap(Object)} multiple times for
+     * the same object will likely return the same model (although there is
+     * no guarantee as the cache items can be cleared any time).
+     */
+    public void setUseModelCache(boolean useCache) {
+        checkModifiable();
+        modelCache.setUseCache(useCache);
+    }
+
+    /**
+     * @since 2.3.21
+     */
+    public boolean getUseModelCache() {
+        return modelCache.getUseCache();
+    }
+
+    /**
+     * 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.
+     *
+     * @since 2.3.21
+     */
+    public Version getIncompatibleImprovements() {
+        return incompatibleImprovements;
+    }
+
     /**
      * Wraps the parameter object to {@link TemplateModel} interface(s). Simple types like numbers, strings, booleans
      * and dates will be wrapped into the corresponding {@code SimpleXxx} classes (like {@link SimpleNumber}).
@@ -104,11 +671,15 @@ public class DefaultObjectWrapper extends org.apache.freemarker.core.model.impl.
     @Override
     public TemplateModel wrap(Object obj) throws TemplateModelException {
         if (obj == null) {
-            return super.wrap(null);
+            return null;
         }
         if (obj instanceof TemplateModel) {
             return (TemplateModel) obj;
         }
+        if (obj instanceof TemplateModelAdapter) {
+            return ((TemplateModelAdapter) obj).getTemplateModel();
+        }
+
         if (obj instanceof String) {
             return new SimpleScalar((String) obj);
         }
@@ -148,68 +719,847 @@ public class DefaultObjectWrapper extends org.apache.freemarker.core.model.impl.
         if (obj instanceof Iterable) {
             return DefaultIterableAdapter.adapt((Iterable<?>) obj, this);
         }
+        if (obj instanceof Enumeration) {
+            return DefaultEnumerationAdapter.adapt((Enumeration<?>) obj, this);
+        }
+
+        // [FM3] Via plugin mechanism, not by default anymore
+        if (obj instanceof Node) {
+            return handW3CNode((Node) obj);
+        }
+
         return handleUnknownType(obj);
     }
-    
+
+    protected TemplateModel handW3CNode(Node node) throws TemplateModelException {
+        return NodeModel.wrap(node);
+    }
+
     /**
-     * Called for an object that isn't considered to be of a "basic" Java type, like for an application specific type,
-     * or for a W3C DOM_WRAPPER node. In its default implementation, W3C {@link Node}-s will be wrapped as {@link NodeModel}-s
-     * (allows DOM_WRAPPER tree traversal), others will be wrapped using {@link BeansWrapper#wrap(Object)}.
-     * 
+     * Called for an object that isn't considered to be of a "basic" Java type, like for an application specific type.
+     * In its default implementation, the object will be wrapped as a generic JavaBean.
+     *
      * <p>
      * When you override this method, you should first decide if you want to wrap the object in a custom way (and if so
      * then do it and return with the result), and if not, then you should call the super method (assuming the default
      * behavior is fine with you).
      */
     protected TemplateModel handleUnknownType(Object obj) throws TemplateModelException {
-        if (obj instanceof Node) {
-            return wrapDomNode(obj);
+        return modelCache.getInstance(obj);
+    }
+
+    /**
+     * Wraps a Java method so that it can be called from templates, without wrapping its parent ("this") object. The
+     * result is almost the same as that you would get by wrapping the parent object then getting the method from the
+     * resulting {@link TemplateHashModel} by name. Except, if the wrapped method is overloaded, with this method you
+     * explicitly select a an overload, while otherwise you would get a {@link TemplateMethodModelEx} that selects an
+     * overload each time it's called based on the argument values.
+     *
+     * @param object The object whose method will be called, or {@code null} if {@code method} is a static method.
+     *          This object will be used "as is", like without unwrapping it if it's a {@link TemplateModelAdapter}.
+     * @param method The method to call, which must be an (inherited) member of the class of {@code object}, as
+     *          described by {@link Method#invoke(Object, Object...)}
+     *
+     * @since 2.3.22
+     */
+    public TemplateMethodModelEx wrap(Object object, Method method) {
+        return new SimpleMethodModel(object, method, method.getParameterTypes(), this);
+    }
+
+    /**
+     * @since 2.3.22
+     */
+    @Override
+    public TemplateHashModel wrapAsAPI(Object obj) throws TemplateModelException {
+        return new APIModel(obj, this);
+    }
+
+    private final ModelFactory BOOLEAN_FACTORY = new ModelFactory() {
+        @Override
+        public TemplateModel create(Object object, ObjectWrapper wrapper) {
+            return ((Boolean) object).booleanValue() ? trueModel : falseModel;
+        }
+    };
+
+    private static final ModelFactory ITERATOR_FACTORY = new ModelFactory() {
+        @Override
+        public TemplateModel create(Object object, ObjectWrapper wrapper) {
+            return new IteratorModel((Iterator<?>) object, (DefaultObjectWrapper) wrapper);
+        }
+    };
+
+    private static final ModelFactory ENUMERATION_FACTORY = new ModelFactory() {
+        @Override
+        public TemplateModel create(Object object, ObjectWrapper wrapper) {
+            return new EnumerationModel((Enumeration<?>) object, (DefaultObjectWrapper) wrapper);
+        }
+    };
+
+    protected ModelFactory getModelFactory(Class<?> clazz) {
+        if (Map.class.isAssignableFrom(clazz)) {
+            return SimpleMapModel.FACTORY;
+        }
+        if (Collection.class.isAssignableFrom(clazz)) {
+            return CollectionModel.FACTORY;
+        }
+        if (Number.class.isAssignableFrom(clazz)) {
+            return NumberModel.FACTORY;
+        }
+        if (Date.class.isAssignableFrom(clazz)) {
+            return DateModel.FACTORY;
+        }
+        if (Boolean.class == clazz) { // Boolean is final
+            return BOOLEAN_FACTORY;
+        }
+        if (ResourceBundle.class.isAssignableFrom(clazz)) {
+            return ResourceBundleModel.FACTORY;
+        }
+        if (Iterator.class.isAssignableFrom(clazz)) {
+            return ITERATOR_FACTORY;
+        }
+        if (Enumeration.class.isAssignableFrom(clazz)) {
+            return ENUMERATION_FACTORY;
+        }
+        if (clazz.isArray()) {
+            return ArrayModel.FACTORY;
+        }
+        return StringModel.FACTORY;
+    }
+
+    /**
+     * Attempts to unwrap a model into underlying object. Generally, this
+     * method is the inverse of the {@link #wrap(Object)} method. In addition
+     * it will unwrap arbitrary {@link TemplateNumberModel} instances into
+     * a number, arbitrary {@link TemplateDateModel} instances into a date,
+     * {@link TemplateScalarModel} instances into a String, arbitrary
+     * {@link TemplateBooleanModel} instances into a Boolean, arbitrary
+     * {@link TemplateHashModel} instances into a Map, arbitrary
+     * {@link TemplateSequenceModel} into a List, and arbitrary
+     * {@link TemplateCollectionModel} into a Set. All other objects are
+     * returned unchanged.
+     * @throws TemplateModelException if an attempted unwrapping fails.
+     */
+    @Override
+    public Object unwrap(TemplateModel model) throws TemplateModelException {
+        return unwrap(model, Object.class);
+    }
+
+    /**
+     * Attempts to unwrap a model into an object of the desired class.
+     * Generally, this method is the inverse of the {@link #wrap(Object)}
+     * method. It recognizes a wide range of target classes - all Java built-in
+     * primitives, primitive wrappers, numbers, dates, sets, lists, maps, and
+     * native arrays.
+     * @param model the model to unwrap
+     * @param targetClass the class of the unwrapped result; {@code Object.class} of we don't know what the expected type is.
+     * @return the unwrapped result of the desired class
+     * @throws TemplateModelException if an attempted unwrapping fails.
+     *
+     * @see #tryUnwrapTo(TemplateModel, Class)
+     */
+    public Object unwrap(TemplateModel model, Class<?> targetClass)
+            throws TemplateModelException {
+        final Object obj = tryUnwrapTo(model, targetClass);
+        if (obj == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+            throw new TemplateModelException("Can not unwrap model of type " +
+                    model.getClass().getName() + " to type " + targetClass.getName());
+        }
+        return obj;
+    }
+
+    /**
+     * @since 2.3.22
+     */
+    @Override
+    public Object tryUnwrapTo(TemplateModel model, Class<?> targetClass) throws TemplateModelException {
+        return tryUnwrapTo(model, targetClass, 0);
+    }
+
+    /**
+     * @param typeFlags
+     *            Used when unwrapping for overloaded methods and so the {@code targetClass} is possibly too generic
+     *            (as it's the most specific common superclass). Must be 0 when unwrapping parameter values for
+     *            non-overloaded methods.
+     * @return {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} or the unwrapped object.
+     */
+    Object tryUnwrapTo(TemplateModel model, Class<?> targetClass, int typeFlags) throws TemplateModelException {
+        Object res = tryUnwrapTo(model, targetClass, typeFlags, null);
+        if ((typeFlags & TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT) != 0
+                && res instanceof Number) {
+            return OverloadedNumberUtil.addFallbackType((Number) res, typeFlags);
+        } else {
+            return res;
+        }
+    }
+
+    /**
+     * See {@link #tryUnwrapTo(TemplateModel, Class, int)}.
+     */
+    private Object tryUnwrapTo(final TemplateModel model, Class<?> targetClass, final int typeFlags,
+                               final Map<Object, Object> recursionStops)
+            throws TemplateModelException {
+        if (model == null) {
+            return null;
+        }
+
+        if (targetClass.isPrimitive()) {
+            targetClass = _ClassUtil.primitiveClassToBoxingClass(targetClass);
+        }
+
+        // This is for transparent interop with other wrappers (and ourselves)
+        // Passing the targetClass allows e.g. a Jython-aware method that declares a
+        // PyObject as its argument to receive a PyObject from a Jython-aware TemplateModel
+        // passed as an argument to TemplateMethodModelEx etc.
+        if (model instanceof AdapterTemplateModel) {
+            Object wrapped = ((AdapterTemplateModel) model).getAdaptedObject(targetClass);
+            if (targetClass == Object.class || targetClass.isInstance(wrapped)) {
+                return wrapped;
+            }
+
+            // Attempt numeric conversion:
+            if (targetClass != Object.class && (wrapped instanceof Number && _ClassUtil.isNumerical(targetClass))) {
+                Number number = forceUnwrappedNumberToType((Number) wrapped, targetClass);
+                if (number != null) return number;
+            }
+        }
+
+        if (model instanceof WrapperTemplateModel) {
+            Object wrapped = ((WrapperTemplateModel) model).getWrappedObject();
+            if (targetClass == Object.class || targetClass.isInstance(wrapped)) {
+                return wrapped;
+            }
+
+            // Attempt numeric conversion:
+            if (targetClass != Object.class && (wrapped instanceof Number && _ClassUtil.isNumerical(targetClass))) {
+                Number number = forceUnwrappedNumberToType((Number) wrapped, targetClass);
+                if (number != null) {
+                    return number;
+                }
+            }
+        }
+
+        // Translation of generic template models to POJOs. First give priority
+        // to various model interfaces based on the targetClass. This helps us
+        // select the appropriate interface in multi-interface models when we
+        // know what is expected as the return type.
+        if (targetClass != Object.class) {
+
+            // [2.4][IcI]: Should also check for CharSequence at the end
+            if (String.class == targetClass) {
+                if (model instanceof TemplateScalarModel) {
+                    return ((TemplateScalarModel) model).getAsString();
+                }
+                // String is final, so no other conversion will work
+                return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
+            }
+
+            // Primitive numeric types & Number.class and its subclasses
+            if (_ClassUtil.isNumerical(targetClass)) {
+                if (model instanceof TemplateNumberModel) {
+                    Number number = forceUnwrappedNumberToType(
+                            ((TemplateNumberModel) model).getAsNumber(), targetClass);
+                    if (number != null) {
+                        return number;
+                    }
+                }
+            }
+
+            if (boolean.class == targetClass || Boolean.class == targetClass) {
+                if (model instanceof TemplateBooleanModel) {
+                    return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean());
+                }
+                // Boolean is final, no other conversion will work
+                return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
+            }
+
+            if (Map.class == targetClass) {
+                if (model instanceof TemplateHashModel) {
+                    return new HashAdapter((TemplateHashModel) model, this);
+                }
+            }
+
+            if (List.class == targetClass) {
+                if (model instanceof TemplateSequenceModel) {
+                    return new SequenceAdapter((TemplateSequenceModel) model, this);
+                }
+            }
+
+            if (Set.class == targetClass) {
+                if (model instanceof TemplateCollectionModel) {
+                    return new SetAdapter((TemplateCollectionModel) model, this);
+                }
+            }
+
+            if (Collection.class == targetClass || Iterable.class == targetClass) {
+                if (model instanceof TemplateCollectionModel) {
+                    return new CollectionAdapter((TemplateCollectionModel) model,
+                            this);
+                }
+                if (model instanceof TemplateSequenceModel) {
+                    return new SequenceAdapter((TemplateSequenceModel) model, this);
+                }
+            }
+
+            // TemplateSequenceModels can be converted to arrays
+            if (targetClass.isArray()) {
+                if (model instanceof TemplateSequenceModel) {
+                    return unwrapSequenceToArray((TemplateSequenceModel) model, targetClass, true, recursionStops);
+                }
+                // array classes are final, no other conversion will work
+                return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
+            }
+
+            // Allow one-char strings to be coerced to characters
+            if (char.class == targetClass || targetClass == Character.class) {
+                if (model instanceof TemplateScalarModel) {
+                    String s = ((TemplateScalarModel) model).getAsString();
+                    if (s.length() == 1) {
+                        return Character.valueOf(s.charAt(0));
+                    }
+                }
+                // Character is final, no other conversion will work
+                return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
+            }
+
+            if (Date.class.isAssignableFrom(targetClass) && model instanceof TemplateDateModel) {
+                Date date = ((TemplateDateModel) model).getAsDate();
+                if (targetClass.isInstance(date)) {
+                    return date;
+                }
+            }
+        }  //  End: if (targetClass != Object.class)
+
+        // Since the targetClass was of no help initially, now we use
+        // a quite arbitrary order in which we walk through the TemplateModel subinterfaces, and unwrapp them to
+        // their "natural" Java correspondent. We still try exclude unwrappings that won't fit the target parameter
+        // type(s). This is mostly important because of multi-typed FTL values that could be unwrapped on multiple ways.
+        int itf = typeFlags; // Iteration's Type Flags. Should be always 0 for non-overloaded and when !is2321Bugfixed.
+        // If itf != 0, we possibly execute the following loop body at twice: once with utilizing itf, and if it has not
+        // returned, once more with itf == 0. Otherwise we execute this once with itf == 0.
+        do {
+            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_NUMBER) != 0)
+                    && model instanceof TemplateNumberModel) {
+                Number number = ((TemplateNumberModel) model).getAsNumber();
+                if (itf != 0 || targetClass.isInstance(number)) {
+                    return number;
+                }
+            }
+            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_DATE) != 0)
+                    && model instanceof TemplateDateModel) {
+                Date date = ((TemplateDateModel) model).getAsDate();
+                if (itf != 0 || targetClass.isInstance(date)) {
+                    return date;
+                }
+            }
+            if ((itf == 0 || (itf & (TypeFlags.ACCEPTS_STRING | TypeFlags.CHARACTER)) != 0)
+                    && model instanceof TemplateScalarModel
+                    && (itf != 0 || targetClass.isAssignableFrom(String.class))) {
+                String strVal = ((TemplateScalarModel) model).getAsString();
+                if (itf == 0 || (itf & TypeFlags.CHARACTER) == 0) {
+                    return strVal;
+                } else { // TypeFlags.CHAR == 1
+                    if (strVal.length() == 1) {
+                        if ((itf & TypeFlags.ACCEPTS_STRING) != 0) {
+                            return new CharacterOrString(strVal);
+                        } else {
+                            return Character.valueOf(strVal.charAt(0));
+                        }
+                    } else if ((itf & TypeFlags.ACCEPTS_STRING) != 0) {
+                        return strVal;
+                    }
+                    // It had to be unwrapped to Character, but the string length wasn't 1 => Fall through
+                }
+            }
+            // Should be earlier than TemplateScalarModel, but we keep it here until FM 2.4 or such
+            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_BOOLEAN) != 0)
+                    && model instanceof TemplateBooleanModel
+                    && (itf != 0 || targetClass.isAssignableFrom(Boolean.class))) {
+                return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean());
+            }
+            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_MAP) != 0)
+                    && model instanceof TemplateHashModel
+                    && (itf != 0 || targetClass.isAssignableFrom(HashAdapter.class))) {
+                return new HashAdapter((TemplateHashModel) model, this);
+            }
+            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_LIST) != 0)
+                    && model instanceof TemplateSequenceModel
+                    && (itf != 0 || targetClass.isAssignableFrom(SequenceAdapter.class))) {
+                return new SequenceAdapter((TemplateSequenceModel) model, this);
+            }
+            if ((itf == 0 || (itf & TypeFlags.ACCEPTS_SET) != 0)
+                    && model instanceof TemplateCollectionModel
+                    && (itf != 0 || targetClass.isAssignableFrom(SetAdapter.class))) {
+                return new SetAdapter((TemplateCollectionModel) model, this);
+            }
+
+            if ((itf & TypeFlags.ACCEPTS_ARRAY) != 0
+                    && model instanceof TemplateSequenceModel) {
+                return new SequenceAdapter((TemplateSequenceModel) model, this);
+            }
+
+            if (itf == 0) {
+                break;
+            }
+            itf = 0; // start 2nd iteration
+        } while (true);
+
+        // Last ditch effort - is maybe the model itself is an instance of the required type?
+        // Note that this will be always true for Object.class targetClass.
+        if (targetClass.isInstance(model)) {
+            return model;
+        }
+
+        return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
+    }
+
+    /**
+     * @param tryOnly
+     *            If {@code true}, if the conversion of an item to the component type isn't possible, the method returns
+     *            {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} instead of throwing a
+     *            {@link TemplateModelException}.
+     */
+    Object unwrapSequenceToArray(
+            TemplateSequenceModel seq, Class<?> arrayClass, boolean tryOnly, Map<Object, Object> recursionStops)
+            throws TemplateModelException {
+        if (recursionStops != null) {
+            Object retval = recursionStops.get(seq);
+            if (retval != null) {
+                return retval;
+            }
+        } else {
+            recursionStops = new IdentityHashMap<>();
+        }
+        Class<?> componentType = arrayClass.getComponentType();
+        Object array = Array.newInstance(componentType, seq.size());
+        recursionStops.put(seq, array);
+        try {
+            final int size = seq.size();
+            for (int i = 0; i < size; i++) {
+                final TemplateModel seqItem = seq.get(i);
+                Object val = tryUnwrapTo(seqItem, componentType, 0, recursionStops);
+                if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+                    if (tryOnly) {
+                        return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS;
+                    } else {
+                        throw new _TemplateModelException(
+                                "Failed to convert ",  new _DelayedFTLTypeDescription(seq),
+                                " object to ", new _DelayedShortClassName(array.getClass()),
+                                ": Problematic sequence item at index ", Integer.valueOf(i) ," with value type: ",
+                                new _DelayedFTLTypeDescription(seqItem));
+                    }
+
+                }
+                Array.set(array, i, val);
+            }
+        } finally {
+            recursionStops.remove(seq);
         }
-        return super.wrap(obj); 
+        return array;
     }
-    
-    public TemplateModel wrapDomNode(Object obj) {
-        return NodeModel.wrap((Node) obj);
+
+    Object listToArray(List<?> list, Class<?> arrayClass, Map<Object, Object> recursionStops)
+            throws TemplateModelException {
+        if (list instanceof SequenceAdapter) {
+            return unwrapSequenceToArray(
+                    ((SequenceAdapter) list).getTemplateSequenceModel(),
+                    arrayClass, false,
+                    recursionStops);
+        }
+
+        if (recursionStops != null) {
+            Object retval = recursionStops.get(list);
+            if (retval != null) {
+                return retval;
+            }
+        } else {
+            recursionStops = new IdentityHashMap<>();
+        }
+        Class<?> componentType = arrayClass.getComponentType();
+        Object array = Array.newInstance(componentType, list.size());
+        recursionStops.put(list, array);
+        try {
+            boolean isComponentTypeExamined = false;
+            boolean isComponentTypeNumerical = false;  // will be filled on demand
+            boolean isComponentTypeList = false;  // will be filled on demand
+            int i = 0;
+            for (Object listItem : list) {
+                if (listItem != null && !componentType.isInstance(listItem)) {
+                    // Type conversion is needed. If we can't do it, we just let it fail at Array.set later.
+                    if (!isComponentTypeExamined) {
+                        isComponentTypeNumerical = _ClassUtil.isNumerical(componentType);
+                        isComponentTypeList = List.class.isAssignableFrom(componentType);
+                        isComponentTypeExamined = true;
+                    }
+                    if (isComponentTypeNumerical && listItem instanceof Number) {
+                        listItem = forceUnwrappedNumberToType((Number) listItem, componentType);
+                    } else if (componentType == String.class && listItem instanceof Character) {
+                        listItem = String.valueOf(((Character) listItem).charValue());
+                    } else if ((componentType == Character.class || componentType == char.class)
+                            && listItem instanceof String) {
+                        String listItemStr = (String) listItem;
+                        if (listItemStr.length() == 1) {
+                            listItem = Character.valueOf(listItemStr.charAt(0));
+                        }
+                    } else if (componentType.isArray()) {
+                        if (listItem instanceof List) {
+                            listItem = listToArray((List<?>) listItem, componentType, recursionStops);
+                        } else if (listItem instanceof TemplateSequenceModel) {
+                            listItem = unwrapSequenceToArray((TemplateSequenceModel) listItem, componentType, false, recursionStops);
+                        }
+                    } else if (isComponentTypeList && listItem.getClass().isArray()) {
+                        listItem = arrayToList(listItem);
+                    }
+                }
+                try {
+                    Array.set(array, i, listItem);
+                } catch (IllegalArgumentException e) {
+                    throw new TemplateModelException(
+                            "Failed to convert " + _ClassUtil.getShortClassNameOfObject(list)
+                                    + " object to " + _ClassUtil.getShortClassNameOfObject(array)
+                                    + ": Problematic List item at index " + i + " with value type: "
+                                    + _ClassUtil.getShortClassNameOfObject(listItem), e);
+                }
+                i++;
+            }
+        } finally {
+            recursionStops.remove(list);
+        }
+        return array;
     }
 
     /**
-     * Converts an array to a java.util.List.
+     * @param array Must be an array (of either a reference or primitive type)
      */
-    protected Object convertArray(Object arr) {
-        // FM 2.4: Use Arrays.asList instead
-        final int size = Array.getLength(arr);
-        ArrayList list = new ArrayList(size);
-        for (int i = 0; i < size; i++) {
-            list.add(Array.get(arr, i));
+    List<?> arrayToList(Object array) throws TemplateModelException {
+        if (array instanceof Object[]) {
+            // Array of any non-primitive type.
+            // Note that an array of non-primitive type is always instanceof Object[].
+            Object[] objArray = (Object[]) array;
+            return objArray.length == 0 ? Collections.EMPTY_LIST : new NonPrimitiveArrayBackedReadOnlyList(objArray);
+        } else {
+            // Array of any primitive type
+            return Array.getLength(array) == 0 ? Collections.EMPTY_LIST : new PrimtiveArrayBackedReadOnlyList(array);
+        }
+    }
+
+    /**
+     * Converts a number to the target type aggressively (possibly with overflow or significant loss of precision).
+     * @param n Non-{@code null}
+     * @return {@code null} if the conversion has failed.
+     */
+    static Number forceUnwrappedNumberToType(final Number n, final Class<?> targetType) {
+        // We try to order the conditions by decreasing probability.
+        if (targetType == n.getClass()) {
+            return n;
+        } else if (targetType == int.class || targetType == Integer.class) {
+            return n instanceof Integer ? (Integer) n : Integer.valueOf(n.intValue());
+        } else if (targetType == long.class || targetType == Long.class) {
+            return n instanceof Long ? (Long) n : Long.valueOf(n.longValue());
+        } else if (targetType == double.class || targetType == Double.class) {
+            return n instanceof Double ? (Double) n : Double.valueOf(n.doubleValue());
+        } else if (targetType == BigDecimal.class) {
+            if (n instanceof BigDecimal) {
+                return n;
+            } else if (n instanceof BigInteger) {
+                return new BigDecimal((BigInteger) n);
+            } else if (n instanceof Long) {
+                // Because we can't represent long accurately as double
+                return BigDecimal.valueOf(n.longValue());
+            } else {
+                return new BigDecimal(n.doubleValue());
+            }
+        } else if (targetType == float.class || targetType == Float.class) {
+            return n instanceof Float ? (Float) n : Float.valueOf(n.floatValue());
+        } else if (targetType == byte.class || targetType == Byte.class) {
+            return n instanceof Byte ? (Byte) n : Byte.valueOf(n.byteValue());
+        } else if (targetType == short.class || targetType == Short.class) {
+            return n instanceof Short ? (Short) n : Short.valueOf(n.shortValue());
+        } else if (targetType == BigInteger.class) {
+            if (n instanceof BigInteger) {
+                return n;
+            } else {
+                if (n instanceof OverloadedNumberUtil.IntegerBigDecimal) {
+                    return ((OverloadedNumberUtil.IntegerBigDecimal) n).bigIntegerValue();
+                } else if (n instanceof BigDecimal) {
+                    return ((BigDecimal) n).toBigInteger();
+                } else {
+                    return BigInteger.valueOf(n.longValue());
+                }
+            }
+        } else {
+            final Number oriN = n instanceof OverloadedNumberUtil.NumberWithFallbackType
+                    ? ((OverloadedNumberUtil.NumberWithFallbackType) n).getSourceNumber() : n;
+            if (targetType.isInstance(oriN)) {
+                // Handle nonstandard Number subclasses as well as directly java.lang.Number.
+                return oriN;
+            } else {
+                // Fails
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Invokes the specified method, wrapping the return value. The specialty
+     * of this method is that if the return value is null, and the return type
+     * of the invoked method is void, {@link TemplateModel#NOTHING} is returned.
+     * @param object the object to invoke the method on
+     * @param method the method to invoke
+     * @param args the arguments to the method
+     * @return the wrapped return value of the method.
+     * @throws InvocationTargetException if the invoked method threw an exception
+     * @throws IllegalAccessException if the method can't be invoked due to an
+     * access restriction.
+     * @throws TemplateModelException if the return value couldn't be wrapped
+     * (this can happen if the wrapper has an outer identity or is subclassed,
+     * and the outer identity or the subclass throws an exception. Plain
+     * DefaultObjectWrapper never throws TemplateModelException).
+     */
+    TemplateModel invokeMethod(Object object, Method method, Object[] args)
+            throws InvocationTargetException,
+            IllegalAccessException,
+            TemplateModelException {
+        // [2.4]: Java's Method.invoke truncates numbers if the target type has not enough bits to hold the value.
+        // There should at least be an option to check this.
+        Object retval = method.invoke(object, args);
+        return
+                method.getReturnType() == void.class
+                        ? TemplateModel.NOTHING
+                        : getOuterIdentity().wrap(retval);
+    }
+
+    /**
+     * Returns a hash model that represents the so-called class static models.
+     * Every class static model is itself a hash through which you can call
+     * static methods on the specified class. To obtain a static model for a
+     * class, get the element of this hash with the fully qualified class name.
+     * For example, if you place this hash model inside the root data model
+     * under name "statics", you can use i.e. <code>statics["java.lang.
+     * System"]. currentTimeMillis()</code> to call the {@link
+     * java.lang.System#currentTimeMillis()} method.
+     * @return a hash model whose keys are fully qualified class names, and
+     * that returns hash models whose elements are the static models of the
+     * classes.
+     */
+    public TemplateHashModel getStaticModels() {
+        return staticModels;
+    }
+
+    /**
+     * Returns a hash model that represents the so-called class enum models.
+     * Every class' enum model is itself a hash through which you can access
+     * enum value declared by the specified class, assuming that class is an
+     * enumeration. To obtain an enum model for a class, get the element of this
+     * hash with the fully qualified class name. For example, if you place this
+     * hash model inside the root data model under name "enums", you can use
+     * i.e. <code>statics["java.math.RoundingMode"].UP</code> to access the
+     * {@link java.math.RoundingMode#UP} value.
+     * @return a hash model whose keys are fully qualified class names, and
+     * that returns hash models whose elements are the enum models of the
+     * classes.
+     */
+    public TemplateHashModel getEnumModels() {
+        return enumModels;
+    }
+
+    /** For Unit tests only */
+    ModelCache getModelCache() {
+        return modelCache;
+    }
+
+    /**
+     * Creates a new instance of the specified class using the method call logic of this object wrapper for calling the
+     * constructor. Overloaded constructors and varargs are supported. Only public constructors will be called.
+     *
+     * @param clazz The class whose constructor we will call.
+     * @param arguments The list of {@link TemplateModel}-s to pass to the constructor after unwrapping them
+     * @return The instance created; it's not wrapped into {@link TemplateModel}.
+     */
+    public Object newInstance(Class<?> clazz, List/*<? extends TemplateModel>*/ arguments)
+            throws TemplateModelException {
+        try {
+            Object ctors = classIntrospector.get(clazz).get(ClassIntrospector.CONSTRUCTORS_KEY);
+            if (ctors == null) {
+                throw new TemplateModelException("Class " + clazz.getName() +
+                        " has no public constructors.");
+            }
+            Constructor<?> ctor = null;
+            Object[] objargs;
+            if (ctors instanceof SimpleMethod) {
+                SimpleMethod sm = (SimpleMethod) ctors;
+                ctor = (Constructor<?>) sm.getMember();
+                objargs = sm.unwrapArguments(arguments, this);
+                try {
+                    return ctor.newInstance(objargs);
+                } catch (Exception e) {
+                    if (e instanceof TemplateModelException) throw (TemplateModelException) e;
+                    throw _MethodUtil.newInvocationTemplateModelException(null, ctor, e);
+                }
+            } else if (ctors instanceof OverloadedMethods) {
+                final MemberAndArguments mma = ((OverloadedMethods) ctors).getMemberAndArguments(arguments, this);
+                try {
+                    return mma.invokeConstructor(this);
+                } catch (Exception e) {
+                    if (e instanceof TemplateModelException) throw (TemplateModelException) e;
+
+                    throw _MethodUtil.newInvocationTemplateModelException(null, mma.getCallableMemberDescriptor(), e);
+                }
+            } else {
+                // Cannot happen
+                throw new BugException();
+            }
+        } catch (TemplateModelException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new TemplateModelException(
+                    "Error while creating new instance of class " + clazz.getName() + "; see cause exception", e);
+        }
+    }
+
+    /**
+     * Removes the introspection data for a class from the cache.
+     * Use this if you know that a class is not used anymore in templates.
+     * If the class will be still used, the cache entry will be silently
+     * re-created, so this isn't a dangerous operation.
+     *
+     * @since 2.3.20
+     */
+    public void removeFromClassIntrospectionCache(Class<?> clazz) {
+        classIntrospector.remove(clazz);
+    }
+
+    /**
+     * Removes all class introspection data from the cache.
+     *
+     * <p>Use this if you want to free up memory on the expense of recreating
+     * the cache entries for the classes that will be used later in templates.
+     *
+     * @throws IllegalStateException if {@link #isClassIntrospectionCacheRestricted()} is {@code true}.
+     *
+     * @since 2.3.20
+     */
+    public void clearClassIntrospecitonCache() {
+        classIntrospector.clearCache();
+    }
+
+    ClassIntrospector getClassIntrospector() {
+        return classIntrospector;
+    }
+
+    /**
+     * Converts any {@link BigDecimal}s in the passed array to the type of
+     * the corresponding formal argument of the method.
+     */
+    // Unused?
+    public static void coerceBigDecimals(AccessibleObject callable, Object[] args) {
+        Class<?>[] formalTypes = null;
+        for (int i = 0; i < args.length; ++i) {
+            Object arg = args[i];
+            if (arg instanceof BigDecimal) {
+                if (formalTypes == null) {
+                    if (callable instanceof Method) {
+                        formalTypes = ((Method) callable).getParameterTypes();
+                    } else if (callable instanceof Constructor) {
+                        formalTypes = ((Constructor<?>) callable).getParameterTypes();
+                    } else {
+                        throw new IllegalArgumentException("Expected method or "
+                                + " constructor; callable is " +
+                                callable.getClass().getName());
+                    }
+                }
+                args[i] = coerceBigDecimal((BigDecimal) arg, formalTypes[i]);
+            }
+        }
+    }
+
+    /**
+     * Converts any {@link BigDecimal}-s in the passed array to the type of
+     * the corresponding formal argument of the method via {@link #coerceBigDecimal(BigDecimal, Class)}.
+     */
+    public static void coerceBigDecimals(Class<?>[] formalTypes, Object[] args) {
+        int typeLen = formalTypes.length;
+        int argsLen = args.length;
+        int min = Math.min(typeLen, argsLen);
+        for (int i = 0; i < min; ++i) {
+            Object arg = args[i];
+            if (arg instanceof BigDecimal) {
+                args[i] = coerceBigDecimal((BigDecimal) arg, formalTypes[i]);
+            }
+        }
+        if (argsLen > typeLen) {
+            Class<?> varArgType = formalTypes[typeLen - 1];
+            for (int i = typeLen; i < argsLen; ++i) {
+                Object arg = args[i];
+                if (arg instanceof BigDecimal) {
+                    args[i] = coerceBigDecimal((BigDecimal) arg, varArgType);
+                }
+            }
+        }
+    }
+
+    /**
+     * Converts {@link BigDecimal} to the class given in the {@code formalType} argument if that's a known numerical
+     * type, returns the {@link BigDecimal} as is otherwise. Overflow and precision loss are possible, similarly as
+     * with casting in Java.
+     */
+    public static Object coerceBigDecimal(BigDecimal bd, Class<?> formalType) {
+        // int is expected in most situations, so we check it first
+        if (formalType == int.class || formalType == Integer.class) {
+            return Integer.valueOf(bd.intValue());
+        } else if (formalType == double.class || formalType == Double.class) {
+            return Double.valueOf(bd.doubleValue());
+        } else if (formalType == long.class || formalType == Long.class) {
+            return Long.valueOf(bd.longValue());
+        } else if (formalType == float.class || formalType == Float.class) {
+            return Float.valueOf(bd.floatValue());
+        } else if (formalType == short.class || formalType == Short.class) {
+            return Short.valueOf(bd.shortValue());
+        } else if (formalType == byte.class || formalType == Byte.class) {
+            return Byte.valueOf(bd.byteValue());
+        } else if (java.math.BigInteger.class.isAssignableFrom(formalType)) {
+            return bd.toBigInteger();
+        } else {
+            return bd;
         }
-        return list;
     }
 
     /**
      * Returns the lowest version number that is equivalent with the parameter version.
-     * 
+     *
      * @since 2.3.22
      */
     protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
-        return BeansWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
+        _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
+        return Configuration.VERSION_3_0_0;
     }
 
+
     /**
-     * @since 2.3.22
+     * Returns the name-value pairs that describe the configuration of this {@link DefaultObjectWrapper}; called from
+     * {@link #toString()}. The expected format is like {@code "foo=bar, baaz=wombat"}. When overriding this, you should
+     * call the super method, and then insert the content before it with a following {@code ", "}, or after it with a
+     * preceding {@code ", "}.
      */
-    @Override
     protected String toPropertiesString() {
-        String bwProps = super.toPropertiesString();
-        
-        // Remove simpleMapWrapper, as its irrelevant for this wrapper:
-        if (bwProps.startsWith("simpleMapWrapper")) {
-            int smwEnd = bwProps.indexOf(',');
-            if (smwEnd != -1) {
-                bwProps = bwProps.substring(smwEnd + 1).trim();
-            }
-        }
-        
-        return "";
+        // Start with "simpleMapWrapper", because the override in DefaultObjectWrapper expects it to be there!
+        return "exposureLevel=" + classIntrospector.getExposureLevel() + ", "
+                + "exposeFields=" + classIntrospector.getExposeFields() + ", "
+                + "sharedClassIntrospCache="
+                + (classIntrospector.isShared() ? "@" + System.identityHashCode(classIntrospector) : "none");
     }
-    
+
+    /**
+     * Returns the exact class name and the identity hash, also the values of the most often used
+     * {@link DefaultObjectWrapper} configuration properties, also if which (if any) shared class introspection
+     * cache it uses.
+     */
+    @Override
+    public String toString() {
+        final String propsStr = toPropertiesString();
+        return _ClassUtil.getShortClassNameOfObject(this) + "@" + System.identityHashCode(this)
+                + "(" + incompatibleImprovements + ", "
+                + (propsStr.length() != 0 ? propsStr + ", ..." : "")
+                + ")";
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
index 40768f8..89af870 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperBuilder.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperBuilder.java
@@ -25,17 +25,85 @@ import java.util.Map;
 import java.util.WeakHashMap;
 
 import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
-import org.apache.freemarker.core.model.impl.beans._BeansAPI;
+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>See {@link BeansWrapperBuilder} for more info, as that works identically. 
- * 
- * @since 2.3.21
+ * 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 {
 
@@ -65,12 +133,20 @@ public class DefaultObjectWrapperBuilder extends DefaultObjectWrapperConfigurati
      * a singleton that is also in use elsewhere. 
      */
     public DefaultObjectWrapper build() {
-        return _BeansAPI.getBeansWrapperSubclassSingleton(
+        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 _BeansAPI._BeansWrapperSubclassFactory<DefaultObjectWrapper, DefaultObjectWrapperConfiguration> {
+        implements _ModelAPI._DefaultObjectWrapperSubclassFactory<DefaultObjectWrapper, DefaultObjectWrapperConfiguration> {
     
         private static final DefaultObjectWrapperFactory INSTANCE = new DefaultObjectWrapperFactory(); 
         


[17/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
index 307d278..a2ca780 100644
--- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java
@@ -20,8 +20,9 @@
 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.impl.beans.BeansWrapperConfiguration;
+import org.apache.freemarker.core.model.TemplateDateModel;
 
 /**
  * Holds {@link DefaultObjectWrapper} configuration settings and defines their defaults.
@@ -29,13 +30,187 @@ import org.apache.freemarker.core.model.impl.beans.BeansWrapperConfiguration;
  * 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.
- * 
- * @since 2.3.22
  */
-public abstract class DefaultObjectWrapperConfiguration extends BeansWrapperConfiguration {
-    
+public abstract class DefaultObjectWrapperConfiguration implements Cloneable {
+
+    private final Version incompatibleImprovements;
+
+    protected ClassIntrospectorBuilder classIntrospectorFactory;
+
+    // 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;
+
+        classIntrospectorFactory = new ClassIntrospectorBuilder(incompatibleImprovements);
+    }
+
+    /**
+     * Same as {@link #DefaultObjectWrapperConfiguration(Version, boolean) DefaultObjectWrapperConfiguration(Version, false)}.
+     */
     protected DefaultObjectWrapperConfiguration(Version incompatibleImprovements) {
-        super(DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements), true);
+        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 + classIntrospectorFactory.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 classIntrospectorFactory.equals(other.classIntrospectorFactory);
+    }
+
+    protected Object clone(boolean deepCloneKey) {
+        try {
+            DefaultObjectWrapperConfiguration clone = (DefaultObjectWrapperConfiguration) super.clone();
+            if (deepCloneKey) {
+                clone.classIntrospectorFactory
+                        = (ClassIntrospectorBuilder) classIntrospectorFactory.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 classIntrospectorFactory.getExposureLevel();
+    }
+
+    /** See {@link DefaultObjectWrapper#setExposureLevel(int)}. */
+    public void setExposureLevel(int exposureLevel) {
+        classIntrospectorFactory.setExposureLevel(exposureLevel);
+    }
+
+    public boolean getExposeFields() {
+        return classIntrospectorFactory.getExposeFields();
+    }
+
+    /** See {@link DefaultObjectWrapper#setExposeFields(boolean)}. */
+    public void setExposeFields(boolean exposeFields) {
+        classIntrospectorFactory.setExposeFields(exposeFields);
+    }
+
+    public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
+        return classIntrospectorFactory.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) {
+        classIntrospectorFactory.setMethodAppearanceFineTuner(methodAppearanceFineTuner);
+    }
+
+    MethodSorter getMethodSorter() {
+        return classIntrospectorFactory.getMethodSorter();
+    }
+
+    void setMethodSorter(MethodSorter methodSorter) {
+        classIntrospectorFactory.setMethodSorter(methodSorter);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java
new file mode 100644
index 0000000..3ec8e87
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+/**
+ * Represents that no member was chosen. Why it wasn't is represented by the two singleton instances,
+ * {@link #NO_SUCH_METHOD} and {@link #AMBIGUOUS_METHOD}. (Note that instances of these are cached associated with the
+ * argument types, thus it shouldn't store details that are specific to the actual argument values. In fact, it better
+ * remains a set of singletons.)     
+ */
+final class EmptyCallableMemberDescriptor extends MaybeEmptyCallableMemberDescriptor {
+    
+    static final EmptyCallableMemberDescriptor NO_SUCH_METHOD = new EmptyCallableMemberDescriptor();
+    static final EmptyCallableMemberDescriptor AMBIGUOUS_METHOD = new EmptyCallableMemberDescriptor();
+    
+    private EmptyCallableMemberDescriptor() { }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java
new file mode 100644
index 0000000..25987d7
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java
@@ -0,0 +1,93 @@
+/*
+ * 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._DelayedOrdinal;
+
+/**
+ * Describes a failed member lookup. Instances of this must not be cached as instances may store the actual argument
+ * values.
+ */
+final class EmptyMemberAndArguments extends MaybeEmptyMemberAndArguments {
+    
+    static final EmptyMemberAndArguments WRONG_NUMBER_OF_ARGUMENTS
+            = new EmptyMemberAndArguments(
+                    "No compatible overloaded variation was found; wrong number of arguments.", true, null);
+    
+    private final Object errorDescription;
+    private final boolean numberOfArgumentsWrong;
+    private final Object[] unwrappedArguments;
+    
+    private EmptyMemberAndArguments(
+            Object errorDescription, boolean numberOfArgumentsWrong, Object[] unwrappedArguments) {
+        this.errorDescription = errorDescription;
+        this.numberOfArgumentsWrong = numberOfArgumentsWrong;
+        this.unwrappedArguments = unwrappedArguments;
+    }
+
+    static EmptyMemberAndArguments noCompatibleOverload(int unwrappableIndex) {
+        return new EmptyMemberAndArguments(
+                new Object[] { "No compatible overloaded variation was found; can't convert (unwrap) the ",
+                new _DelayedOrdinal(Integer.valueOf(unwrappableIndex)), " argument to the desired Java type." },
+                false,
+                null);
+    }
+    
+    static EmptyMemberAndArguments noCompatibleOverload(Object[] unwrappedArgs) {
+        return new EmptyMemberAndArguments(
+                "No compatible overloaded variation was found; declared parameter types and argument value types mismatch.",
+                false,
+                unwrappedArgs);
+    }
+
+    static EmptyMemberAndArguments ambiguous(Object[] unwrappedArgs) {
+        return new EmptyMemberAndArguments(
+                "Multiple compatible overloaded variations were found with the same priority.",
+                false,
+                unwrappedArgs);
+    }
+
+    static MaybeEmptyMemberAndArguments from(
+            EmptyCallableMemberDescriptor emtpyMemberDesc, Object[] unwrappedArgs) {
+        if (emtpyMemberDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) {
+            return noCompatibleOverload(unwrappedArgs);
+        } else if (emtpyMemberDesc == EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD) {
+            return ambiguous(unwrappedArgs);
+        } else {
+            throw new IllegalArgumentException("Unrecognized constant: " + emtpyMemberDesc);
+        }
+    }
+
+    Object getErrorDescription() {
+        return errorDescription;
+    }
+
+    /**
+     * @return {@code null} if the error has occurred earlier than the full argument list was unwrapped.
+     */
+    Object[] getUnwrappedArguments() {
+        return unwrappedArguments;
+    }
+
+    public boolean isNumberOfArgumentsWrong() {
+        return numberOfArgumentsWrong;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/EnumerationModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/EnumerationModel.java b/src/main/java/org/apache/freemarker/core/model/impl/EnumerationModel.java
new file mode 100644
index 0000000..d7b14cc
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/EnumerationModel.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+
+/**
+ * <p>A class that adds {@link TemplateModelIterator} functionality to the
+ * {@link Enumeration} interface implementers. 
+ * </p> <p>Using the model as a collection model is NOT thread-safe, as 
+ * enumerations are inherently not thread-safe.
+ * Further, you can iterate over it only once. Attempts to call the
+ * {@link #iterator()} method after it was already driven to the end once will 
+ * throw an exception.</p>
+ */
+
+public class EnumerationModel
+extends
+    BeanModel
+implements
+    TemplateModelIterator,
+    TemplateCollectionModel {
+    private boolean accessed = false;
+    
+    /**
+     * Creates a new model that wraps the specified enumeration object.
+     * @param enumeration the enumeration object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     */
+    public EnumerationModel(Enumeration enumeration, DefaultObjectWrapper wrapper) {
+        super(enumeration, wrapper);
+    }
+
+    /**
+     * This allows the enumeration to be used in a <tt>&lt;#list&gt;</tt> block.
+     * @return "this"
+     */
+    @Override
+    public TemplateModelIterator iterator() throws TemplateModelException {
+        synchronized (this) {
+            if (accessed) {
+                throw new TemplateModelException(
+                    "This collection is stateful and can not be iterated over the" +
+                    " second time.");
+            }
+            accessed = true;
+        }
+        return this;
+    }
+    
+    /**
+     * Calls underlying {@link Enumeration#nextElement()}.
+     */
+    @Override
+    public boolean hasNext() {
+        return ((Enumeration) object).hasMoreElements();
+    }
+
+
+    /**
+     * Calls underlying {@link Enumeration#nextElement()} and wraps the result.
+     */
+    @Override
+    public TemplateModel next()
+    throws TemplateModelException {
+        try {
+            return wrap(((Enumeration) object).nextElement());
+        } catch (NoSuchElementException e) {
+            throw new TemplateModelException(
+                "No more elements in the enumeration.");
+        }
+    }
+
+    /**
+     * Returns {@link Enumeration#hasMoreElements()}. Therefore, an
+     * enumeration that has no more element evaluates to false, and an 
+     * enumeration that has further elements evaluates to true.
+     */
+    public boolean getAsBoolean() {
+        return hasNext();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
new file mode 100644
index 0000000..7fefec9
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
@@ -0,0 +1,180 @@
+/*
+ * 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.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ */
+public class HashAdapter extends AbstractMap implements TemplateModelAdapter {
+    private final DefaultObjectWrapper wrapper;
+    private final TemplateHashModel model;
+    private Set entrySet;
+    
+    HashAdapter(TemplateHashModel model, DefaultObjectWrapper wrapper) {
+        this.model = model;
+        this.wrapper = wrapper;
+    }
+    
+    @Override
+    public TemplateModel getTemplateModel() {
+        return model;
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        try {
+            return model.isEmpty();
+        } catch (TemplateModelException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+    
+    @Override
+    public Object get(Object key) {
+        try {
+            return wrapper.unwrap(model.get(String.valueOf(key)));
+        } catch (TemplateModelException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        // A quick check that doesn't require TemplateHashModelEx 
+        if (get(key) != null) {
+            return true;
+        }
+        return super.containsKey(key);
+    }
+    
+    @Override
+    public Set entrySet() {
+        if (entrySet != null) {
+            return entrySet;
+        }
+        return entrySet = new AbstractSet() {
+            @Override
+            public Iterator iterator() {
+                final TemplateModelIterator i;
+                try {
+                     i = getModelEx().keys().iterator();
+                } catch (TemplateModelException e) {
+                    throw new UndeclaredThrowableException(e);
+                }
+                return new Iterator() {
+                    @Override
+                    public boolean hasNext() {
+                        try {
+                            return i.hasNext();
+                        } catch (TemplateModelException e) {
+                            throw new UndeclaredThrowableException(e);
+                        }
+                    }
+                    
+                    @Override
+                    public Object next() {
+                        final Object key;
+                        try {
+                            key = wrapper.unwrap(i.next());
+                        } catch (TemplateModelException e) {
+                            throw new UndeclaredThrowableException(e);
+                        }
+                        return new Map.Entry() {
+                            @Override
+                            public Object getKey() {
+                                return key;
+                            }
+                            
+                            @Override
+                            public Object getValue() {
+                                return get(key);
+                            }
+                            
+                            @Override
+                            public Object setValue(Object value) {
+                                throw new UnsupportedOperationException();
+                            }
+                            
+                            @Override
+                            public boolean equals(Object o) {
+                                if (!(o instanceof Map.Entry))
+                                    return false;
+                                Map.Entry e = (Map.Entry) o;
+                                Object k1 = getKey();
+                                Object k2 = e.getKey();
+                                if (k1 == k2 || (k1 != null && k1.equals(k2))) {
+                                    Object v1 = getValue();
+                                    Object v2 = e.getValue();
+                                    if (v1 == v2 || (v1 != null && v1.equals(v2))) 
+                                        return true;
+                                }
+                                return false;
+                            }
+                        
+                            @Override
+                            public int hashCode() {
+                                Object value = getValue();
+                                return (key == null ? 0 : key.hashCode()) ^
+                                       (value == null ? 0 : value.hashCode());
+                            }
+                        };
+                    }
+                    
+                    @Override
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+            
+            @Override
+            public int size() {
+                try {
+                    return getModelEx().size();
+                } catch (TemplateModelException e) {
+                    throw new UndeclaredThrowableException(e);
+                }
+            }
+        };
+    }
+    
+    private TemplateHashModelEx getModelEx() {
+        if (model instanceof TemplateHashModelEx) {
+            return ((TemplateHashModelEx) model);
+        }
+        throw new UnsupportedOperationException(
+                "Operation supported only on TemplateHashModelEx. " + 
+                model.getClass().getName() + " does not implement it though.");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java b/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java
new file mode 100644
index 0000000..41197ca
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.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.model.impl;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * An exception thrown when there is an attempt to access
+ * an invalid bean property when we are in a "strict bean" mode
+ */
+
+public class InvalidPropertyException extends TemplateModelException {
+	
+    public InvalidPropertyException(String description) {
+        super(description);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/IteratorModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/IteratorModel.java b/src/main/java/org/apache/freemarker/core/model/impl/IteratorModel.java
new file mode 100644
index 0000000..0eabf21
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/IteratorModel.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+
+/**
+ * <p>A class that adds {@link TemplateModelIterator} functionality to the
+ * {@link Iterator} interface implementers. 
+ * </p>
+ * <p>It differs from the {@link org.apache.freemarker.core.model.impl.SimpleCollection} in that 
+ * it inherits from {@link BeanModel}, and therefore you can call methods on 
+ * it directly, even to the effect of calling <tt>iterator.remove()</tt> in 
+ * the template.</p> <p>Using the model as a collection model is NOT 
+ * thread-safe, as iterators are inherently not thread-safe.
+ * Further, you can iterate over it only once. Attempts to call the
+ * {@link #iterator()} method after it was already driven to the end once will 
+ * throw an exception.</p>
+ */
+
+public class IteratorModel
+extends
+    BeanModel
+implements
+    TemplateModelIterator,
+    TemplateCollectionModel {
+    private boolean accessed = false;
+    
+    /**
+     * Creates a new model that wraps the specified iterator object.
+     * @param iterator the iterator object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     */
+    public IteratorModel(Iterator iterator, DefaultObjectWrapper wrapper) {
+        super(iterator, wrapper);
+    }
+
+    /**
+     * This allows the iterator to be used in a <tt>&lt;#list&gt;</tt> block.
+     * @return "this"
+     */
+    @Override
+    public TemplateModelIterator iterator() throws TemplateModelException {
+        synchronized (this) {
+            if (accessed) {
+                throw new TemplateModelException(
+                    "This collection is stateful and can not be iterated over the" +
+                    " second time.");
+            }
+            accessed = true;
+        }
+        return this;
+    }
+    
+    /**
+     * Calls underlying {@link Iterator#hasNext()}.
+     */
+    @Override
+    public boolean hasNext() {
+        return ((Iterator) object).hasNext();
+    }
+
+
+    /**
+     * Calls underlying {@link Iterator#next()} and wraps the result.
+     */
+    @Override
+    public TemplateModel next()
+    throws TemplateModelException {
+        try {
+            return wrap(((Iterator) object).next());
+        } catch (NoSuchElementException e) {
+            throw new TemplateModelException(
+                "No more elements in the iterator.", e);
+        }
+    }
+
+    /**
+     * Returns {@link Iterator#hasNext()}. Therefore, an
+     * iterator that has no more element evaluates to false, and an 
+     * iterator that has further elements evaluates to true.
+     */
+    public boolean getAsBoolean() {
+        return hasNext();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java b/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java
new file mode 100644
index 0000000..5f8e222
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.ref.WeakReference;
+
+import org.zeroturnaround.javarebel.ClassEventListener;
+import org.zeroturnaround.javarebel.ReloaderFactory;
+
+class JRebelClassChangeNotifier implements ClassChangeNotifier {
+
+    static void testAvailability() {
+        ReloaderFactory.getInstance();
+    }
+
+    @Override
+    public void subscribe(ClassIntrospector classIntrospector) {
+        ReloaderFactory.getInstance().addClassReloadListener(
+                new ClassIntrospectorCacheInvalidator(classIntrospector));
+    }
+
+    private static class ClassIntrospectorCacheInvalidator
+            implements ClassEventListener {
+        private final WeakReference ref;
+
+        ClassIntrospectorCacheInvalidator(ClassIntrospector w) {
+            ref = new WeakReference(w);
+        }
+
+        @Override
+        public void onClassEvent(int eventType, Class pClass) {
+            ClassIntrospector ci = (ClassIntrospector) ref.get();
+            if (ci == null) {
+                ReloaderFactory.getInstance().removeClassReloadListener(this);
+            } else if (eventType == ClassEventListener.EVENT_RELOADED) {
+                ci.remove(pClass);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MapModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MapModel.java b/src/main/java/org/apache/freemarker/core/model/impl/MapModel.java
new file mode 100644
index 0000000..95abb2a
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/MapModel.java
@@ -0,0 +1,120 @@
+/*
+ * 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.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * <p>A special case of {@link BeanModel} that adds implementation
+ * for {@link TemplateMethodModelEx} on map objects that is a shortcut for the
+ * <tt>Map.get()</tt> method. Note that if the passed argument itself is a
+ * reflection-wrapper model, then the map lookup will be performed using the
+ * wrapped object as the key. Note that you can call <tt>get()</tt> using the
+ * <tt>map.key</tt> syntax inherited from {@link BeanModel} as well, 
+ * however in that case the key is always a string.</p>
+ * <p>The class itself does not implement the {@link org.apache.freemarker.core.model.TemplateCollectionModel}.
+ * You can, however use <tt>map.entrySet()</tt>, <tt>map.keySet()</tt>, or
+ * <tt>map.values()</tt> to obtain {@link org.apache.freemarker.core.model.TemplateCollectionModel} instances for 
+ * various aspects of the map.</p>
+ */
+public class MapModel
+extends
+    StringModel
+implements
+    TemplateMethodModelEx {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new MapModel((Map) object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+
+    /**
+     * Creates a new model that wraps the specified map object.
+     * @param map the map object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     */
+    public MapModel(Map map, DefaultObjectWrapper wrapper) {
+        super(map, wrapper);
+    }
+
+    /**
+     * The first argument is used as a key to call the map's <tt>get</tt> method.
+     */
+    @Override
+    public Object exec(List arguments)
+    throws TemplateModelException {
+        Object key = unwrap((TemplateModel) arguments.get(0));
+        return wrap(((Map) object).get(key));
+    }
+
+    /**
+     * Overridden to invoke the generic get method by casting to Map instead of 
+     * through reflection - should yield better performance.
+     */
+    @Override
+    protected TemplateModel invokeGenericGet(Map keyMap, Class clazz, String key)
+    throws TemplateModelException {
+        Map map = (Map) object;
+        Object val = map.get(key);
+        if (val == null) {
+            if (key.length() == 1) {
+                // just check for Character key if this is a single-character string
+                Character charKey = Character.valueOf(key.charAt(0));
+                val = map.get(charKey);
+                if (val == null && !(map.containsKey(key) || map.containsKey(charKey))) {
+                    return UNKNOWN;
+                }
+            } else if (!map.containsKey(key)) {
+                return UNKNOWN;
+            }
+        }
+        return wrap(val);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return ((Map) object).isEmpty() && super.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return keySet().size();
+    }
+
+    @Override
+    protected Set keySet() {
+        Set set = super.keySet();
+        set.addAll(((Map) object).keySet());
+        return set;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java
new file mode 100644
index 0000000..1b62bf8
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+/**
+ * Superclass of the {@link EmptyCallableMemberDescriptor} and {@link CallableMemberDescriptor} "case classes".
+ */
+abstract class MaybeEmptyCallableMemberDescriptor { }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java
new file mode 100644
index 0000000..bfc88ee
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+abstract class MaybeEmptyMemberAndArguments { }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java
new file mode 100644
index 0000000..e7c735c
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ */
+class MemberAndArguments extends MaybeEmptyMemberAndArguments {
+    
+    private final CallableMemberDescriptor callableMemberDesc;
+    private final Object[] args;
+    
+    /**
+     * @param args The already unwrapped arguments
+     */
+    MemberAndArguments(CallableMemberDescriptor callableMemberDesc, Object[] args) {
+        this.callableMemberDesc = callableMemberDesc;
+        this.args = args;
+    }
+    
+    /**
+     * The already unwrapped arguments.
+     */
+    Object[] getArgs() {
+        return args;
+    }
+    
+    TemplateModel invokeMethod(DefaultObjectWrapper ow, Object obj)
+            throws TemplateModelException, InvocationTargetException, IllegalAccessException {
+        return callableMemberDesc.invokeMethod(ow, obj, args);
+    }
+
+    Object invokeConstructor(DefaultObjectWrapper ow)
+            throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
+            TemplateModelException {
+        return callableMemberDesc.invokeConstructor(ow, args);
+    }
+    
+    CallableMemberDescriptor getCallableMemberDescriptor() {
+        return callableMemberDesc;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java b/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
new file mode 100644
index 0000000..3235c51
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
@@ -0,0 +1,156 @@
+/*
+ * 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.IndexedPropertyDescriptor;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+
+/**
+ * Used for customizing how the methods are visible from templates, via
+ * {@link DefaultObjectWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}.
+ * The object that implements this should also implement {@link SingletonCustomizer} whenever possible.
+ * 
+ * @since 2.3.21
+ */
+public interface MethodAppearanceFineTuner {
+
+    /**
+     * Implement this to tweak certain aspects of how methods appear in the
+     * data-model. {@link DefaultObjectWrapper} will pass in all Java methods here that
+     * it intends to expose in the data-model as methods (so you can do
+     * <tt>obj.foo()</tt> in the template).
+     * With this method you can do the following tweaks:
+     * <ul>
+     *   <li>Hide a method that would be otherwise shown by calling
+     *     {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeMethodAs(String)}
+     *     with <tt>null</tt> parameter. Note that you can't un-hide methods
+     *     that are not public or are considered to by unsafe
+     *     (like {@link Object#wait()}) because
+     *     {@link #process} is not called for those.</li>
+     *   <li>Show the method with a different name in the data-model than its
+     *     real name by calling
+     *     {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeMethodAs(String)}
+     *     with non-<tt>null</tt> parameter.
+     *   <li>Create a fake JavaBean property for this method by calling
+     *     {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeAsProperty(PropertyDescriptor)}.
+     *     For example, if you have <tt>int size()</tt> in a class, but you
+     *     want it to be accessed from the templates as <tt>obj.size</tt>,
+     *     rather than as <tt>obj.size()</tt>, you can do that with this.
+     *     The default is {@code null}, which means that no fake property is
+     *     created for the method. You need not and shouldn't set this
+     *     to non-<tt>null</tt> for the getter methods of real JavaBean
+     *     properties, as those are automatically shown as properties anyway.
+     *     The property name in the {@link PropertyDescriptor} can be anything,
+     *     but the method (or methods) in it must belong to the class that
+     *     is given as the <tt>clazz</tt> parameter or it must be inherited from
+     *     that class, or else whatever errors can occur later.
+     *     {@link IndexedPropertyDescriptor}-s are supported.
+     *     If a real JavaBean property of the same name exists, it won't be
+     *     replaced by the fake one. Also if a fake property of the same name
+     *     was assigned earlier, it won't be replaced.
+     *   <li>Prevent the method to hide a JavaBean property (fake or real) of
+     *     the same name by calling
+     *     {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setMethodShadowsProperty(boolean)}
+     *     with <tt>false</tt>. The default is <tt>true</tt>, so if you have
+     *     both a property and a method called "foo", then in the template
+     *     <tt>myObject.foo</tt> will return the method itself instead
+     *     of the property value, which is often undesirable.
+     * </ul>
+     * 
+     * <p>Note that you can expose a Java method both as a method and as a
+     * JavaBean property on the same time, however you have to chose different
+     * names for them to prevent shadowing. 
+     * 
+     * @param in Describes the method about which the decision will have to be made.
+     *  
+     * @param out Stores how the method will be exposed in the
+     *   data-model after {@link #process} returns.
+     *   This is initialized so that it reflects the default
+     *   behavior of {@link DefaultObjectWrapper}, so you don't have to do anything with this
+     *   when you don't want to change the default behavior.
+     */
+    void process(DecisionInput in, Decision out);
+
+    /**
+     * Used for {@link MethodAppearanceFineTuner#process} to store the results.
+     */
+    final class Decision {
+        private PropertyDescriptor exposeAsProperty;
+        private String exposeMethodAs;
+        private boolean methodShadowsProperty;
+
+        void setDefaults(Method m) {
+            exposeAsProperty = null;
+            exposeMethodAs = m.getName();
+            methodShadowsProperty = true;
+        }
+
+        public PropertyDescriptor getExposeAsProperty() {
+            return exposeAsProperty;
+        }
+
+        public void setExposeAsProperty(PropertyDescriptor exposeAsProperty) {
+            this.exposeAsProperty = exposeAsProperty;
+        }
+
+        public String getExposeMethodAs() {
+            return exposeMethodAs;
+        }
+
+        public void setExposeMethodAs(String exposeMethodAs) {
+            this.exposeMethodAs = exposeMethodAs;
+        }
+
+        public boolean getMethodShadowsProperty() {
+            return methodShadowsProperty;
+        }
+
+        public void setMethodShadowsProperty(boolean methodShadowsProperty) {
+            this.methodShadowsProperty = methodShadowsProperty;
+        }
+
+    }
+
+    /**
+     * Used for {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner#process} as input parameter.
+     */
+    final class DecisionInput {
+        private Method method;
+        private Class<?> containingClass;
+
+        void setMethod(Method method) {
+            this.method = method;
+        }
+
+        void setContainingClass(Class<?> containingClass) {
+            this.containingClass = containingClass;
+        }
+
+        public Method getMethod() {
+            return method;
+        }
+
+        public Class/*<?>*/ getContainingClass() {
+            return containingClass;
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..b893bfe
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
@@ -0,0 +1,32 @@
+/*
+ * 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.MethodDescriptor;
+
+/**
+ * Used for JUnit testing method-order dependence bugs via
+ * {@link DefaultObjectWrapperBuilder#setMethodSorter(MethodSorter)}.
+ */
+interface MethodSorter {
+
+    MethodDescriptor[] sortMethodDescriptors(MethodDescriptor[] methodDescriptors);
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ModelCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ModelCache.java b/src/main/java/org/apache/freemarker/core/model/impl/ModelCache.java
new file mode 100644
index 0000000..529b988
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ModelCache.java
@@ -0,0 +1,143 @@
+/*
+ * 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.SoftReference;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+
+/**
+ * Internally used by various wrapper implementations to implement model
+ * caching.
+ */
+public abstract class ModelCache {
+    private boolean useCache = false;
+    private Map<Object, ModelReference> modelCache = null;
+    private ReferenceQueue<TemplateModel> refQueue = null;
+    
+    protected ModelCache() {
+    }
+    
+    /**
+     * Sets whether this wrapper caches model instances. Default is false.
+     * When set to true, calling {@link #getInstance(Object)} 
+     * multiple times for the same object will return the same model.
+     */
+    public synchronized void setUseCache(boolean useCache) {
+        this.useCache = useCache;
+        if (useCache) {
+            modelCache = new IdentityHashMap<>();
+            refQueue = new ReferenceQueue<>();
+        } else {
+            modelCache = null;
+            refQueue = null;
+        }
+    }
+
+    /**
+     * @since 2.3.21
+     */
+    public synchronized boolean getUseCache() {
+        return useCache;
+    }
+    
+    public TemplateModel getInstance(Object object) {
+        if (object instanceof TemplateModel) {
+            return (TemplateModel) object;
+        }
+        if (object instanceof TemplateModelAdapter) {
+            return ((TemplateModelAdapter) object).getTemplateModel();
+        }
+        if (useCache && isCacheable(object)) {
+            TemplateModel model = lookup(object);
+            if (model == null) {
+                model = create(object);
+                register(model, object);
+            }
+            return model;
+        } else {
+            return create(object);
+        }
+    }
+    
+    protected abstract TemplateModel create(Object object);
+    protected abstract boolean isCacheable(Object object);
+    
+    public void clearCache() {
+        if (modelCache != null) {
+            synchronized (modelCache) {
+                modelCache.clear();
+            }
+        }
+    }
+
+    private TemplateModel lookup(Object object) {
+        ModelReference ref = null;
+        // NOTE: we're doing minimal synchronizations -- which can lead to
+        // duplicate wrapper creation. However, this has no harmful side-effects and
+        // is a lesser performance hit.
+        synchronized (modelCache) {
+            ref = modelCache.get(object);
+        }
+
+        if (ref != null)
+            return ref.getModel();
+
+        return null;
+    }
+
+    private void register(TemplateModel model, Object object) {
+        synchronized (modelCache) {
+            // Remove cleared references
+            for (; ; ) {
+                ModelReference queuedRef = (ModelReference) refQueue.poll();
+                if (queuedRef == null) {
+                    break;
+                }
+                modelCache.remove(queuedRef.object);
+            }
+            // Register new reference
+            modelCache.put(object, new ModelReference(model, object, refQueue));
+        }
+    }
+
+    /**
+     * A special soft reference that is registered in the modelCache.
+     * When it gets cleared (that is, the model became unreachable)
+     * it will remove itself from the model cache.
+     */
+    private static final class ModelReference extends SoftReference<TemplateModel> {
+        Object object;
+
+        ModelReference(TemplateModel ref, Object object, ReferenceQueue<TemplateModel> refQueue) {
+            super(ref, refQueue);
+            this.object = object;
+        }
+
+        TemplateModel getModel() {
+            return get();
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ModelFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ModelFactory.java b/src/main/java/org/apache/freemarker/core/model/impl/ModelFactory.java
new file mode 100644
index 0000000..cd74342
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/ModelFactory.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.model.impl;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Interface used to create various wrapper models in the {@link ModelCache}.
+ */
+public interface ModelFactory {
+    /**
+     * Create a wrapping model for the specified object that belongs to
+     * the specified wrapper.
+     */
+    TemplateModel create(Object object, ObjectWrapper wrapper);
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java b/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java
new file mode 100644
index 0000000..2babe9f
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.AbstractList;
+
+class NonPrimitiveArrayBackedReadOnlyList extends AbstractList {
+    
+    private final Object[] array;
+    
+    NonPrimitiveArrayBackedReadOnlyList(Object[] array) {
+        this.array = array;
+    }
+
+    @Override
+    public Object get(int index) {
+        return array[index];
+    }
+
+    @Override
+    public int size() {
+        return array.length;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/NumberModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/NumberModel.java b/src/main/java/org/apache/freemarker/core/model/impl/NumberModel.java
new file mode 100644
index 0000000..70b50d9
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/NumberModel.java
@@ -0,0 +1,60 @@
+/*
+ * 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.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+/**
+ * Wraps arbitrary subclass of {@link java.lang.Number} into a reflective model.
+ * Beside acting as a {@link TemplateNumberModel}, you can call all Java methods on
+ * these objects as well.
+ */
+public class NumberModel
+extends
+    BeanModel
+implements
+    TemplateNumberModel {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new NumberModel((Number) object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+    /**
+     * Creates a new model that wraps the specified number object.
+     * @param number the number object to wrap into a model.
+     * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+     * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+     * model gains many attributes from its wrapper, including the caching 
+     * behavior, method exposure level, method-over-item shadowing policy etc.
+     */
+    public NumberModel(Number number, DefaultObjectWrapper wrapper) {
+        super(number, wrapper);
+    }
+
+    @Override
+    public Number getAsNumber() {
+        return (Number) object;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
new file mode 100644
index 0000000..5b160b6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
@@ -0,0 +1,99 @@
+/*
+ * 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.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Stores the non-varargs methods for a {@link OverloadedMethods} object.
+ */
+class OverloadedFixArgsMethods extends OverloadedMethodsSubset {
+    
+    OverloadedFixArgsMethods() {
+        super();
+    }
+
+    @Override
+    Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) {
+        return memberDesc.getParamTypes();
+    }
+    
+    @Override
+    void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) {
+        // Do nothing
+    }
+
+    @Override
+    MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, DefaultObjectWrapper unwrapper)
+    throws TemplateModelException {
+        if (tmArgs == null) {
+            // null is treated as empty args
+            tmArgs = Collections.EMPTY_LIST;
+        }
+        final int argCount = tmArgs.size();
+        final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();
+        if (unwrappingHintsByParamCount.length <= argCount) {
+            return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS;
+        }
+        Class[] unwarppingHints = unwrappingHintsByParamCount[argCount];
+        if (unwarppingHints == null) {
+            return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS;
+        }
+        
+        Object[] pojoArgs = new Object[argCount];
+        
+        int[] typeFlags = getTypeFlags(argCount);
+        if (typeFlags == ALL_ZEROS_ARRAY) {
+            typeFlags = null;
+        }
+
+        Iterator it = tmArgs.iterator();
+        for (int i = 0; i < argCount; ++i) {
+            Object pojo = unwrapper.tryUnwrapTo(
+                    (TemplateModel) it.next(),
+                    unwarppingHints[i],
+                    typeFlags != null ? typeFlags[i] : 0);
+            if (pojo == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+                return EmptyMemberAndArguments.noCompatibleOverload(i + 1);
+            }
+            pojoArgs[i] = pojo;
+        }
+        
+        MaybeEmptyCallableMemberDescriptor maybeEmtpyMemberDesc = getMemberDescriptorForArgs(pojoArgs, false);
+        if (maybeEmtpyMemberDesc instanceof CallableMemberDescriptor) {
+            CallableMemberDescriptor memberDesc = (CallableMemberDescriptor) maybeEmtpyMemberDesc;
+            if (typeFlags != null) {
+                // Note that overloaded method selection has already accounted for overflow errors when the method
+                // was selected. So this forced conversion shouldn't cause such corruption. Except, conversion from
+                // BigDecimal is allowed to overflow for backward-compatibility.
+                forceNumberArgumentsToParameterTypes(pojoArgs, memberDesc.getParamTypes(), typeFlags);
+            }
+            return new MemberAndArguments(memberDesc, pojoArgs);
+        } else {
+            return EmptyMemberAndArguments.from((EmptyCallableMemberDescriptor) maybeEmtpyMemberDesc, pojoArgs);
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
new file mode 100644
index 0000000..e352bcf
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.freemarker.core._DelayedConversionToString;
+import org.apache.freemarker.core._ErrorDescriptionBuilder;
+import org.apache.freemarker.core._TemplateModelException;
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util.FTLUtil;
+import org.apache.freemarker.core.util._ClassUtil;
+
+/**
+ * Used instead of {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} for overloaded methods and
+ * constructors.
+ * 
+ * <p>After the initialization with the {@link #addMethod(Method)} and {@link #addConstructor(Constructor)} calls are
+ * done, the instance must be thread-safe. Before that, it's the responsibility of the caller of those methods to
+ * ensure that the object is properly publishing to other threads.
+ */
+final class OverloadedMethods {
+
+    private final OverloadedMethodsSubset fixArgMethods;
+    private OverloadedMethodsSubset varargMethods;
+    
+    OverloadedMethods() {
+        fixArgMethods = new OverloadedFixArgsMethods();
+    }
+    
+    void addMethod(Method method) {
+        final Class[] paramTypes = method.getParameterTypes();
+        addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(method, paramTypes));
+    }
+
+    void addConstructor(Constructor constr) {
+        final Class[] paramTypes = constr.getParameterTypes();
+        addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(constr, paramTypes));
+    }
+    
+    private void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) {
+        // Note: "varargs" methods are always callable as oms args, with a sequence (array) as the last parameter.
+        fixArgMethods.addCallableMemberDescriptor(memberDesc);
+        if (memberDesc.isVarargs()) {
+            if (varargMethods == null) {
+                varargMethods = new OverloadedVarArgsMethods();
+            }
+            varargMethods.addCallableMemberDescriptor(memberDesc);
+        }
+    }
+    
+    MemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs, DefaultObjectWrapper unwrapper)
+    throws TemplateModelException {
+        // Try to find a oms args match:
+        MaybeEmptyMemberAndArguments fixArgsRes = fixArgMethods.getMemberAndArguments(tmArgs, unwrapper);
+        if (fixArgsRes instanceof MemberAndArguments) {
+            return (MemberAndArguments) fixArgsRes;
+        }
+
+        // Try to find a varargs match:
+        MaybeEmptyMemberAndArguments varargsRes;
+        if (varargMethods != null) {
+            varargsRes = varargMethods.getMemberAndArguments(tmArgs, unwrapper);
+            if (varargsRes instanceof MemberAndArguments) {
+                return (MemberAndArguments) varargsRes;
+            }
+        } else {
+            varargsRes = null;
+        }
+        
+        _ErrorDescriptionBuilder edb = new _ErrorDescriptionBuilder(
+                toCompositeErrorMessage(
+                        (EmptyMemberAndArguments) fixArgsRes,
+                        (EmptyMemberAndArguments) varargsRes,
+                        tmArgs),
+                "\nThe matching overload was searched among these members:\n",
+                memberListToString());
+        addMarkupBITipAfterNoNoMarchIfApplicable(edb, tmArgs);
+        throw new _TemplateModelException(edb);
+    }
+
+    private Object[] toCompositeErrorMessage(
+            final EmptyMemberAndArguments fixArgsEmptyRes, final EmptyMemberAndArguments varargsEmptyRes,
+            List tmArgs) {
+        final Object[] argsErrorMsg;
+        if (varargsEmptyRes != null) {
+            if (fixArgsEmptyRes == null || fixArgsEmptyRes.isNumberOfArgumentsWrong()) {
+                argsErrorMsg = toErrorMessage(varargsEmptyRes, tmArgs);
+            } else {
+                argsErrorMsg = new Object[] {
+                        "When trying to call the non-varargs overloads:\n",
+                        toErrorMessage(fixArgsEmptyRes, tmArgs),
+                        "\nWhen trying to call the varargs overloads:\n",
+                        toErrorMessage(varargsEmptyRes, null)
+                };
+            }
+        } else {
+            argsErrorMsg = toErrorMessage(fixArgsEmptyRes, tmArgs);
+        }
+        return argsErrorMsg;
+    }
+
+    private Object[] toErrorMessage(EmptyMemberAndArguments res, List/*<TemplateModel>*/ tmArgs) {
+        final Object[] unwrappedArgs = res.getUnwrappedArguments();
+        return new Object[] {
+                res.getErrorDescription(),
+                tmArgs != null
+                        ? new Object[] {
+                                "\nThe FTL type of the argument values were: ", getTMActualParameterTypes(tmArgs), "." }
+                        : "",
+                unwrappedArgs != null
+                        ? new Object[] {
+                                "\nThe Java type of the argument values were: ",
+                                getUnwrappedActualParameterTypes(unwrappedArgs) + "." }
+                        : ""};
+    }
+
+    private _DelayedConversionToString memberListToString() {
+        return new _DelayedConversionToString(null) {
+            
+            @Override
+            protected String doConversion(Object obj) {
+                final Iterator fixArgMethodsIter = fixArgMethods.getMemberDescriptors();
+                final Iterator varargMethodsIter = varargMethods != null ? varargMethods.getMemberDescriptors() : null;
+                
+                boolean hasMethods = fixArgMethodsIter.hasNext() || (varargMethodsIter != null && varargMethodsIter.hasNext()); 
+                if (hasMethods) {
+                    StringBuilder sb = new StringBuilder();
+                    HashSet fixArgMethods = new HashSet();
+                    while (fixArgMethodsIter.hasNext()) {
+                        if (sb.length() != 0) sb.append(",\n");
+                        sb.append("    ");
+                        CallableMemberDescriptor callableMemberDesc = (CallableMemberDescriptor) fixArgMethodsIter.next();
+                        fixArgMethods.add(callableMemberDesc);
+                        sb.append(callableMemberDesc.getDeclaration());
+                    }
+                    if (varargMethodsIter != null) {
+                        while (varargMethodsIter.hasNext()) {
+                            CallableMemberDescriptor callableMemberDesc = (CallableMemberDescriptor) varargMethodsIter.next();
+                            if (!fixArgMethods.contains(callableMemberDesc)) {
+                                if (sb.length() != 0) sb.append(",\n");
+                                sb.append("    ");
+                                sb.append(callableMemberDesc.getDeclaration());
+                            }
+                        }
+                    }
+                    return sb.toString();
+                } else {
+                    return "No members";
+                }
+            }
+            
+        };
+    }
+    
+    /**
+     * Adds tip to the error message if converting a {@link TemplateMarkupOutputModel} argument to {@link String} might
+     * allows finding a matching overload. 
+     */
+    private void addMarkupBITipAfterNoNoMarchIfApplicable(_ErrorDescriptionBuilder edb,
+            List tmArgs) {
+        for (int argIdx = 0; argIdx < tmArgs.size(); argIdx++) {
+            Object tmArg = tmArgs.get(argIdx);
+            if (tmArg instanceof TemplateMarkupOutputModel) {
+                for (Iterator membDescs = fixArgMethods.getMemberDescriptors(); membDescs.hasNext();) {
+                    CallableMemberDescriptor membDesc = (CallableMemberDescriptor) membDescs.next();
+                    Class[] paramTypes = membDesc.getParamTypes();
+                    
+                    Class paramType = null;
+                    if (membDesc.isVarargs() && argIdx >= paramTypes.length - 1) {
+                        paramType = paramTypes[paramTypes.length - 1];
+                        if (paramType.isArray()) {
+                            paramType = paramType.getComponentType();
+                        }
+                    }
+                    if (paramType == null && argIdx < paramTypes.length) {
+                        paramType = paramTypes[argIdx];
+                    }
+                    if (paramType != null) {
+                        if (paramType.isAssignableFrom(String.class) && !paramType.isAssignableFrom(tmArg.getClass())) {
+                            edb.tip(SimpleMethodModel.MARKUP_OUTPUT_TO_STRING_TIP);
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private _DelayedConversionToString getTMActualParameterTypes(List arguments) {
+        final String[] argumentTypeDescs = new String[arguments.size()];
+        for (int i = 0; i < arguments.size(); i++) {
+            argumentTypeDescs[i] = FTLUtil.getTypeDescription((TemplateModel) arguments.get(i));
+        }
+        
+        return new DelayedCallSignatureToString(argumentTypeDescs) {
+
+            @Override
+            String argumentToString(Object argType) {
+                return (String) argType;
+            }
+            
+        };
+    }
+    
+    private Object getUnwrappedActualParameterTypes(Object[] unwrappedArgs) {
+        final Class[] argumentTypes = new Class[unwrappedArgs.length];
+        for (int i = 0; i < unwrappedArgs.length; i++) {
+            Object unwrappedArg = unwrappedArgs[i];
+            argumentTypes[i] = unwrappedArg != null ? unwrappedArg.getClass() : null;
+        }
+        
+        return new DelayedCallSignatureToString(argumentTypes) {
+
+            @Override
+            String argumentToString(Object argType) {
+                return argType != null
+                        ? _ClassUtil.getShortClassName((Class) argType)
+                        : _ClassUtil.getShortClassNameOfObject(null);
+            }
+            
+        };
+    }
+    
+    private abstract class DelayedCallSignatureToString extends _DelayedConversionToString {
+
+        public DelayedCallSignatureToString(Object[] argTypeArray) {
+            super(argTypeArray);
+        }
+
+        @Override
+        protected String doConversion(Object obj) {
+            Object[] argTypes = (Object[]) obj;
+            
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < argTypes.length; i++) {
+                if (i != 0) sb.append(", ");
+                sb.append(argumentToString(argTypes[i]));
+            }
+            
+            return sb.toString();
+        }
+        
+        abstract String argumentToString(Object argType);
+        
+    }
+
+}


[03/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/Java7MembersOnlyBeansWrapper.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/Java7MembersOnlyBeansWrapper.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/Java7MembersOnlyBeansWrapper.java
deleted file mode 100644
index b4cff32..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/Java7MembersOnlyBeansWrapper.java
+++ /dev/null
@@ -1,102 +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.beans;
-
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
-
-/**
- * A hack to keep some unit tests producing exactly the same results on Java 8 as on Java 5-7, by hiding members
- * added after Java 7.
- */
-public class Java7MembersOnlyBeansWrapper extends BeansWrapper {
-    
-    private static final Set<String> POST_JAVA_7_MAP_METHODS = newHashSet(
-            "compute", "computeIfAbsent", "computeIfPresent",
-            "forEach", "getOrDefault", "merge", "putIfAbsent", "replace", "replaceAll");
-
-    private static final Set<String> POST_JAVA_7_ITERABLE_METHODS = newHashSet("forEach");
-    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() {
-
-        @Override
-        public void process(MethodAppearanceDecisionInput in, MethodAppearanceDecision out) {
-            Method m = in.getMethod();
-            Class declCl = m.getDeclaringClass();
-            if (Map.class.isAssignableFrom(declCl)) {
-                if (POST_JAVA_7_MAP_METHODS.contains(m.getName())) {
-                    hideMember(out);
-                    return;
-                }
-            }
-            if (Iterable.class.isAssignableFrom(declCl)) {
-                if (POST_JAVA_7_ITERABLE_METHODS.contains(m.getName())) {
-                    hideMember(out);
-                    return;
-                }
-            }
-            if (Collection.class.isAssignableFrom(declCl)) {
-                if (POST_JAVA_7_COLLECTION_METHODS.contains(m.getName())) {
-                    hideMember(out);
-                    return;
-                }
-            }
-            if (List.class.isAssignableFrom(declCl)) {
-                if (POST_JAVA_7_LIST_METHODS.contains(m.getName())) {
-                    hideMember(out);
-                    return;
-                }
-            }
-        }
-
-        private void hideMember(MethodAppearanceDecision out) {
-            out.setExposeMethodAs(null);
-            out.setExposeAsProperty(null);
-        }
-        
-    };
-    
-    public Java7MembersOnlyBeansWrapper(Version version) {
-        super(version);
-        setMethodAppearanceFineTuner(POST_JAVA_7_FILTER);
-    }
-
-    private static <T> Set<T> newHashSet(T... items) {
-        HashSet<T> r = new HashSet<>();
-        for (T item : items) {
-            r.add(item);
-        }
-        return r;
-    }
-
-    public Java7MembersOnlyBeansWrapper() {
-        this(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/ManyObjectsOfDifferentClasses.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/ManyObjectsOfDifferentClasses.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/ManyObjectsOfDifferentClasses.java
deleted file mode 100644
index 149bf22..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/ManyObjectsOfDifferentClasses.java
+++ /dev/null
@@ -1,249 +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.beans;
-
-// FMPP template source:
-// ----
-// package org.apache.freemarker.core.model.impl.beans;
-// 
-// // FMPP template source:
-// // ----
-// <#assign src><#include "ManyObjectsOfDifferentClasses.java.ftl" parse=false></#assign>
-// ${src?trim?replace('(.*\r?\n)|.+', r'// $0', 'r')}
-// // ----
-// 
-// <#assign MAX_CLA = 8 - 1>
-// <#assign MAX_MET = 10 - 1>
-// <#assign MAX_PRO = 10 - 1>
-// 
-// public class ManyObjectsOfDifferentClasses {
-// 
-//     public static final Object[] OBJECTS = new Object[] {
-//         <#list 0..MAX_CLA as claI>
-//         new C${claI}(),
-//         </#list>
-//     };
-// 
-//     <#list 0..MAX_CLA as claI>
-//     static public class C${claI} {
-//         <#list 0..MAX_PRO as proI>
-//         public int getP${proI}() { return ${claI} * 1000 + ${proI}; }
-//         </#list>
-//         <#list 0..MAX_MET as metI>
-//         public int m${metI}() { return ${claI} * 1000 + ${metI}; };
-//         </#list>
-//     }
-//     </#list>
-//     
-// }
-// ----
-
-
-public class ManyObjectsOfDifferentClasses {
-
-    public static final Object[] OBJECTS = new Object[] {
-        new C0(),
-        new C1(),
-        new C2(),
-        new C3(),
-        new C4(),
-        new C5(),
-        new C6(),
-        new C7(),
-    };
-
-    static public class C0 {
-        public int getP0() { return 0 * 1000 + 0; }
-        public int getP1() { return 0 * 1000 + 1; }
-        public int getP2() { return 0 * 1000 + 2; }
-        public int getP3() { return 0 * 1000 + 3; }
-        public int getP4() { return 0 * 1000 + 4; }
-        public int getP5() { return 0 * 1000 + 5; }
-        public int getP6() { return 0 * 1000 + 6; }
-        public int getP7() { return 0 * 1000 + 7; }
-        public int getP8() { return 0 * 1000 + 8; }
-        public int getP9() { return 0 * 1000 + 9; }
-        public int m0() { return 0 * 1000 + 0; };
-        public int m1() { return 0 * 1000 + 1; };
-        public int m2() { return 0 * 1000 + 2; };
-        public int m3() { return 0 * 1000 + 3; };
-        public int m4() { return 0 * 1000 + 4; };
-        public int m5() { return 0 * 1000 + 5; };
-        public int m6() { return 0 * 1000 + 6; };
-        public int m7() { return 0 * 1000 + 7; };
-        public int m8() { return 0 * 1000 + 8; };
-        public int m9() { return 0 * 1000 + 9; };
-    }
-    static public class C1 {
-        public int getP0() { return 1 * 1000 + 0; }
-        public int getP1() { return 1 * 1000 + 1; }
-        public int getP2() { return 1 * 1000 + 2; }
-        public int getP3() { return 1 * 1000 + 3; }
-        public int getP4() { return 1 * 1000 + 4; }
-        public int getP5() { return 1 * 1000 + 5; }
-        public int getP6() { return 1 * 1000 + 6; }
-        public int getP7() { return 1 * 1000 + 7; }
-        public int getP8() { return 1 * 1000 + 8; }
-        public int getP9() { return 1 * 1000 + 9; }
-        public int m0() { return 1 * 1000 + 0; };
-        public int m1() { return 1 * 1000 + 1; };
-        public int m2() { return 1 * 1000 + 2; };
-        public int m3() { return 1 * 1000 + 3; };
-        public int m4() { return 1 * 1000 + 4; };
-        public int m5() { return 1 * 1000 + 5; };
-        public int m6() { return 1 * 1000 + 6; };
-        public int m7() { return 1 * 1000 + 7; };
-        public int m8() { return 1 * 1000 + 8; };
-        public int m9() { return 1 * 1000 + 9; };
-    }
-    static public class C2 {
-        public int getP0() { return 2 * 1000 + 0; }
-        public int getP1() { return 2 * 1000 + 1; }
-        public int getP2() { return 2 * 1000 + 2; }
-        public int getP3() { return 2 * 1000 + 3; }
-        public int getP4() { return 2 * 1000 + 4; }
-        public int getP5() { return 2 * 1000 + 5; }
-        public int getP6() { return 2 * 1000 + 6; }
-        public int getP7() { return 2 * 1000 + 7; }
-        public int getP8() { return 2 * 1000 + 8; }
-        public int getP9() { return 2 * 1000 + 9; }
-        public int m0() { return 2 * 1000 + 0; };
-        public int m1() { return 2 * 1000 + 1; };
-        public int m2() { return 2 * 1000 + 2; };
-        public int m3() { return 2 * 1000 + 3; };
-        public int m4() { return 2 * 1000 + 4; };
-        public int m5() { return 2 * 1000 + 5; };
-        public int m6() { return 2 * 1000 + 6; };
-        public int m7() { return 2 * 1000 + 7; };
-        public int m8() { return 2 * 1000 + 8; };
-        public int m9() { return 2 * 1000 + 9; };
-    }
-    static public class C3 {
-        public int getP0() { return 3 * 1000 + 0; }
-        public int getP1() { return 3 * 1000 + 1; }
-        public int getP2() { return 3 * 1000 + 2; }
-        public int getP3() { return 3 * 1000 + 3; }
-        public int getP4() { return 3 * 1000 + 4; }
-        public int getP5() { return 3 * 1000 + 5; }
-        public int getP6() { return 3 * 1000 + 6; }
-        public int getP7() { return 3 * 1000 + 7; }
-        public int getP8() { return 3 * 1000 + 8; }
-        public int getP9() { return 3 * 1000 + 9; }
-        public int m0() { return 3 * 1000 + 0; };
-        public int m1() { return 3 * 1000 + 1; };
-        public int m2() { return 3 * 1000 + 2; };
-        public int m3() { return 3 * 1000 + 3; };
-        public int m4() { return 3 * 1000 + 4; };
-        public int m5() { return 3 * 1000 + 5; };
-        public int m6() { return 3 * 1000 + 6; };
-        public int m7() { return 3 * 1000 + 7; };
-        public int m8() { return 3 * 1000 + 8; };
-        public int m9() { return 3 * 1000 + 9; };
-    }
-    static public class C4 {
-        public int getP0() { return 4 * 1000 + 0; }
-        public int getP1() { return 4 * 1000 + 1; }
-        public int getP2() { return 4 * 1000 + 2; }
-        public int getP3() { return 4 * 1000 + 3; }
-        public int getP4() { return 4 * 1000 + 4; }
-        public int getP5() { return 4 * 1000 + 5; }
-        public int getP6() { return 4 * 1000 + 6; }
-        public int getP7() { return 4 * 1000 + 7; }
-        public int getP8() { return 4 * 1000 + 8; }
-        public int getP9() { return 4 * 1000 + 9; }
-        public int m0() { return 4 * 1000 + 0; };
-        public int m1() { return 4 * 1000 + 1; };
-        public int m2() { return 4 * 1000 + 2; };
-        public int m3() { return 4 * 1000 + 3; };
-        public int m4() { return 4 * 1000 + 4; };
-        public int m5() { return 4 * 1000 + 5; };
-        public int m6() { return 4 * 1000 + 6; };
-        public int m7() { return 4 * 1000 + 7; };
-        public int m8() { return 4 * 1000 + 8; };
-        public int m9() { return 4 * 1000 + 9; };
-    }
-    static public class C5 {
-        public int getP0() { return 5 * 1000 + 0; }
-        public int getP1() { return 5 * 1000 + 1; }
-        public int getP2() { return 5 * 1000 + 2; }
-        public int getP3() { return 5 * 1000 + 3; }
-        public int getP4() { return 5 * 1000 + 4; }
-        public int getP5() { return 5 * 1000 + 5; }
-        public int getP6() { return 5 * 1000 + 6; }
-        public int getP7() { return 5 * 1000 + 7; }
-        public int getP8() { return 5 * 1000 + 8; }
-        public int getP9() { return 5 * 1000 + 9; }
-        public int m0() { return 5 * 1000 + 0; };
-        public int m1() { return 5 * 1000 + 1; };
-        public int m2() { return 5 * 1000 + 2; };
-        public int m3() { return 5 * 1000 + 3; };
-        public int m4() { return 5 * 1000 + 4; };
-        public int m5() { return 5 * 1000 + 5; };
-        public int m6() { return 5 * 1000 + 6; };
-        public int m7() { return 5 * 1000 + 7; };
-        public int m8() { return 5 * 1000 + 8; };
-        public int m9() { return 5 * 1000 + 9; };
-    }
-    static public class C6 {
-        public int getP0() { return 6 * 1000 + 0; }
-        public int getP1() { return 6 * 1000 + 1; }
-        public int getP2() { return 6 * 1000 + 2; }
-        public int getP3() { return 6 * 1000 + 3; }
-        public int getP4() { return 6 * 1000 + 4; }
-        public int getP5() { return 6 * 1000 + 5; }
-        public int getP6() { return 6 * 1000 + 6; }
-        public int getP7() { return 6 * 1000 + 7; }
-        public int getP8() { return 6 * 1000 + 8; }
-        public int getP9() { return 6 * 1000 + 9; }
-        public int m0() { return 6 * 1000 + 0; };
-        public int m1() { return 6 * 1000 + 1; };
-        public int m2() { return 6 * 1000 + 2; };
-        public int m3() { return 6 * 1000 + 3; };
-        public int m4() { return 6 * 1000 + 4; };
-        public int m5() { return 6 * 1000 + 5; };
-        public int m6() { return 6 * 1000 + 6; };
-        public int m7() { return 6 * 1000 + 7; };
-        public int m8() { return 6 * 1000 + 8; };
-        public int m9() { return 6 * 1000 + 9; };
-    }
-    static public class C7 {
-        public int getP0() { return 7 * 1000 + 0; }
-        public int getP1() { return 7 * 1000 + 1; }
-        public int getP2() { return 7 * 1000 + 2; }
-        public int getP3() { return 7 * 1000 + 3; }
-        public int getP4() { return 7 * 1000 + 4; }
-        public int getP5() { return 7 * 1000 + 5; }
-        public int getP6() { return 7 * 1000 + 6; }
-        public int getP7() { return 7 * 1000 + 7; }
-        public int getP8() { return 7 * 1000 + 8; }
-        public int getP9() { return 7 * 1000 + 9; }
-        public int m0() { return 7 * 1000 + 0; };
-        public int m1() { return 7 * 1000 + 1; };
-        public int m2() { return 7 * 1000 + 2; };
-        public int m3() { return 7 * 1000 + 3; };
-        public int m4() { return 7 * 1000 + 4; };
-        public int m5() { return 7 * 1000 + 5; };
-        public int m6() { return 7 * 1000 + 6; };
-        public int m7() { return 7 * 1000 + 7; };
-        public int m8() { return 7 * 1000 + 8; };
-        public int m9() { return 7 * 1000 + 9; };
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/ManyStaticsOfDifferentClasses.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/ManyStaticsOfDifferentClasses.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/ManyStaticsOfDifferentClasses.java
deleted file mode 100644
index bffe56d..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/ManyStaticsOfDifferentClasses.java
+++ /dev/null
@@ -1,236 +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.beans;
-
-// FMPP template source:
-// ----
-// package org.apache.freemarker.core.model.impl.beans;
-// 
-// // FMPP template source:
-// // ----
-// <#assign src><#include "ManyStaticsOfDifferentClasses.java.ftl" parse=false></#assign>
-// ${src?trim?replace('(.*\r?\n)|.+', r'// $0', 'r')}
-// // ----
-// 
-// <#assign MAX_CLA = 8 - 1>
-// <#assign MAX_MET = 10 - 1>
-// <#assign MAX_PRO = 10 - 1>
-// 
-// public class ManyStaticsOfDifferentClasses {
-// 
-//     private ManyStaticsOfDifferentClasses() { }
-// 
-//     <#list 0..MAX_CLA as claI>
-//     static public class C${claI} {
-//         <#list 0..MAX_PRO as proI>
-//         public static int p${proI} = ${claI} * 1000 + ${proI};
-//         </#list>
-//         <#list 0..MAX_MET as metI>
-//         public static int m${metI}() { return ${claI} * 1000 + ${metI}; };
-//         </#list>
-//     }
-//     </#list>
-//     
-// }
-// ----
-
-
-public class ManyStaticsOfDifferentClasses {
-
-    private ManyStaticsOfDifferentClasses() { }
-
-    static public class C0 {
-        public static int p0 = 0 * 1000 + 0;
-        public static int p1 = 0 * 1000 + 1;
-        public static int p2 = 0 * 1000 + 2;
-        public static int p3 = 0 * 1000 + 3;
-        public static int p4 = 0 * 1000 + 4;
-        public static int p5 = 0 * 1000 + 5;
-        public static int p6 = 0 * 1000 + 6;
-        public static int p7 = 0 * 1000 + 7;
-        public static int p8 = 0 * 1000 + 8;
-        public static int p9 = 0 * 1000 + 9;
-        public static int m0() { return 0 * 1000 + 0; };
-        public static int m1() { return 0 * 1000 + 1; };
-        public static int m2() { return 0 * 1000 + 2; };
-        public static int m3() { return 0 * 1000 + 3; };
-        public static int m4() { return 0 * 1000 + 4; };
-        public static int m5() { return 0 * 1000 + 5; };
-        public static int m6() { return 0 * 1000 + 6; };
-        public static int m7() { return 0 * 1000 + 7; };
-        public static int m8() { return 0 * 1000 + 8; };
-        public static int m9() { return 0 * 1000 + 9; };
-    }
-    static public class C1 {
-        public static int p0 = 1 * 1000 + 0;
-        public static int p1 = 1 * 1000 + 1;
-        public static int p2 = 1 * 1000 + 2;
-        public static int p3 = 1 * 1000 + 3;
-        public static int p4 = 1 * 1000 + 4;
-        public static int p5 = 1 * 1000 + 5;
-        public static int p6 = 1 * 1000 + 6;
-        public static int p7 = 1 * 1000 + 7;
-        public static int p8 = 1 * 1000 + 8;
-        public static int p9 = 1 * 1000 + 9;
-        public static int m0() { return 1 * 1000 + 0; };
-        public static int m1() { return 1 * 1000 + 1; };
-        public static int m2() { return 1 * 1000 + 2; };
-        public static int m3() { return 1 * 1000 + 3; };
-        public static int m4() { return 1 * 1000 + 4; };
-        public static int m5() { return 1 * 1000 + 5; };
-        public static int m6() { return 1 * 1000 + 6; };
-        public static int m7() { return 1 * 1000 + 7; };
-        public static int m8() { return 1 * 1000 + 8; };
-        public static int m9() { return 1 * 1000 + 9; };
-    }
-    static public class C2 {
-        public static int p0 = 2 * 1000 + 0;
-        public static int p1 = 2 * 1000 + 1;
-        public static int p2 = 2 * 1000 + 2;
-        public static int p3 = 2 * 1000 + 3;
-        public static int p4 = 2 * 1000 + 4;
-        public static int p5 = 2 * 1000 + 5;
-        public static int p6 = 2 * 1000 + 6;
-        public static int p7 = 2 * 1000 + 7;
-        public static int p8 = 2 * 1000 + 8;
-        public static int p9 = 2 * 1000 + 9;
-        public static int m0() { return 2 * 1000 + 0; };
-        public static int m1() { return 2 * 1000 + 1; };
-        public static int m2() { return 2 * 1000 + 2; };
-        public static int m3() { return 2 * 1000 + 3; };
-        public static int m4() { return 2 * 1000 + 4; };
-        public static int m5() { return 2 * 1000 + 5; };
-        public static int m6() { return 2 * 1000 + 6; };
-        public static int m7() { return 2 * 1000 + 7; };
-        public static int m8() { return 2 * 1000 + 8; };
-        public static int m9() { return 2 * 1000 + 9; };
-    }
-    static public class C3 {
-        public static int p0 = 3 * 1000 + 0;
-        public static int p1 = 3 * 1000 + 1;
-        public static int p2 = 3 * 1000 + 2;
-        public static int p3 = 3 * 1000 + 3;
-        public static int p4 = 3 * 1000 + 4;
-        public static int p5 = 3 * 1000 + 5;
-        public static int p6 = 3 * 1000 + 6;
-        public static int p7 = 3 * 1000 + 7;
-        public static int p8 = 3 * 1000 + 8;
-        public static int p9 = 3 * 1000 + 9;
-        public static int m0() { return 3 * 1000 + 0; };
-        public static int m1() { return 3 * 1000 + 1; };
-        public static int m2() { return 3 * 1000 + 2; };
-        public static int m3() { return 3 * 1000 + 3; };
-        public static int m4() { return 3 * 1000 + 4; };
-        public static int m5() { return 3 * 1000 + 5; };
-        public static int m6() { return 3 * 1000 + 6; };
-        public static int m7() { return 3 * 1000 + 7; };
-        public static int m8() { return 3 * 1000 + 8; };
-        public static int m9() { return 3 * 1000 + 9; };
-    }
-    static public class C4 {
-        public static int p0 = 4 * 1000 + 0;
-        public static int p1 = 4 * 1000 + 1;
-        public static int p2 = 4 * 1000 + 2;
-        public static int p3 = 4 * 1000 + 3;
-        public static int p4 = 4 * 1000 + 4;
-        public static int p5 = 4 * 1000 + 5;
-        public static int p6 = 4 * 1000 + 6;
-        public static int p7 = 4 * 1000 + 7;
-        public static int p8 = 4 * 1000 + 8;
-        public static int p9 = 4 * 1000 + 9;
-        public static int m0() { return 4 * 1000 + 0; };
-        public static int m1() { return 4 * 1000 + 1; };
-        public static int m2() { return 4 * 1000 + 2; };
-        public static int m3() { return 4 * 1000 + 3; };
-        public static int m4() { return 4 * 1000 + 4; };
-        public static int m5() { return 4 * 1000 + 5; };
-        public static int m6() { return 4 * 1000 + 6; };
-        public static int m7() { return 4 * 1000 + 7; };
-        public static int m8() { return 4 * 1000 + 8; };
-        public static int m9() { return 4 * 1000 + 9; };
-    }
-    static public class C5 {
-        public static int p0 = 5 * 1000 + 0;
-        public static int p1 = 5 * 1000 + 1;
-        public static int p2 = 5 * 1000 + 2;
-        public static int p3 = 5 * 1000 + 3;
-        public static int p4 = 5 * 1000 + 4;
-        public static int p5 = 5 * 1000 + 5;
-        public static int p6 = 5 * 1000 + 6;
-        public static int p7 = 5 * 1000 + 7;
-        public static int p8 = 5 * 1000 + 8;
-        public static int p9 = 5 * 1000 + 9;
-        public static int m0() { return 5 * 1000 + 0; };
-        public static int m1() { return 5 * 1000 + 1; };
-        public static int m2() { return 5 * 1000 + 2; };
-        public static int m3() { return 5 * 1000 + 3; };
-        public static int m4() { return 5 * 1000 + 4; };
-        public static int m5() { return 5 * 1000 + 5; };
-        public static int m6() { return 5 * 1000 + 6; };
-        public static int m7() { return 5 * 1000 + 7; };
-        public static int m8() { return 5 * 1000 + 8; };
-        public static int m9() { return 5 * 1000 + 9; };
-    }
-    static public class C6 {
-        public static int p0 = 6 * 1000 + 0;
-        public static int p1 = 6 * 1000 + 1;
-        public static int p2 = 6 * 1000 + 2;
-        public static int p3 = 6 * 1000 + 3;
-        public static int p4 = 6 * 1000 + 4;
-        public static int p5 = 6 * 1000 + 5;
-        public static int p6 = 6 * 1000 + 6;
-        public static int p7 = 6 * 1000 + 7;
-        public static int p8 = 6 * 1000 + 8;
-        public static int p9 = 6 * 1000 + 9;
-        public static int m0() { return 6 * 1000 + 0; };
-        public static int m1() { return 6 * 1000 + 1; };
-        public static int m2() { return 6 * 1000 + 2; };
-        public static int m3() { return 6 * 1000 + 3; };
-        public static int m4() { return 6 * 1000 + 4; };
-        public static int m5() { return 6 * 1000 + 5; };
-        public static int m6() { return 6 * 1000 + 6; };
-        public static int m7() { return 6 * 1000 + 7; };
-        public static int m8() { return 6 * 1000 + 8; };
-        public static int m9() { return 6 * 1000 + 9; };
-    }
-    static public class C7 {
-        public static int p0 = 7 * 1000 + 0;
-        public static int p1 = 7 * 1000 + 1;
-        public static int p2 = 7 * 1000 + 2;
-        public static int p3 = 7 * 1000 + 3;
-        public static int p4 = 7 * 1000 + 4;
-        public static int p5 = 7 * 1000 + 5;
-        public static int p6 = 7 * 1000 + 6;
-        public static int p7 = 7 * 1000 + 7;
-        public static int p8 = 7 * 1000 + 8;
-        public static int p9 = 7 * 1000 + 9;
-        public static int m0() { return 7 * 1000 + 0; };
-        public static int m1() { return 7 * 1000 + 1; };
-        public static int m2() { return 7 * 1000 + 2; };
-        public static int m3() { return 7 * 1000 + 3; };
-        public static int m4() { return 7 * 1000 + 4; };
-        public static int m5() { return 7 * 1000 + 5; };
-        public static int m6() { return 7 * 1000 + 6; };
-        public static int m7() { return 7 * 1000 + 7; };
-        public static int m8() { return 7 * 1000 + 8; };
-        public static int m9() { return 7 * 1000 + 9; };
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/MiscNumericalOperationsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/MiscNumericalOperationsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/MiscNumericalOperationsTest.java
deleted file mode 100644
index d88777f..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/MiscNumericalOperationsTest.java
+++ /dev/null
@@ -1,111 +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.beans;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-import org.junit.Assert;
-
-import junit.framework.TestCase;
-
-public class MiscNumericalOperationsTest extends TestCase {
-
-    public MiscNumericalOperationsTest(String name) {
-        super(name);
-    }
-    
-    public void testForceUnwrappedNumberToType() {
-        // Usual type to to all other types:
-        Double n = Double.valueOf(123.75);
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Short.class), Short.valueOf(n.shortValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Short.TYPE), Short.valueOf(n.shortValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Byte.class), Byte.valueOf(n.byteValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Byte.TYPE), Byte.valueOf(n.byteValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Integer.class), Integer.valueOf(n.intValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Integer.TYPE), Integer.valueOf(n.intValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Long.class), Long.valueOf(n.longValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Long.TYPE), Long.valueOf(n.longValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Float.class), Float.valueOf(n.floatValue()));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, Float.TYPE), Float.valueOf(n.floatValue()));
-        assertTrue(BeansWrapper.forceUnwrappedNumberToType(n, Double.class) == n);
-        assertTrue(BeansWrapper.forceUnwrappedNumberToType(n, Double.TYPE) == n);
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, BigInteger.class), new BigInteger("123"));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(n, BigDecimal.class), new BigDecimal("123.75"));
-        
-        // Cases of conversion to BigDecimal:
-        BigDecimal bd = new BigDecimal("123");
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(new BigInteger("123"), BigDecimal.class), bd);
-        assertTrue(BeansWrapper.forceUnwrappedNumberToType(bd, BigDecimal.class) == bd);
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(Long.valueOf(123), BigDecimal.class), bd);
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(Double.valueOf(123), BigDecimal.class), bd);
-        
-        // Cases of conversion to BigInteger:
-        BigInteger bi = new BigInteger("123");
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(new BigDecimal("123.6"), BigInteger.class), bi);
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(
-                new OverloadedNumberUtil.IntegerBigDecimal(new BigDecimal("123")), BigInteger.class), bi);
-        assertTrue(BeansWrapper.forceUnwrappedNumberToType(bi, BigInteger.class) == bi);
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(Long.valueOf(123), BigInteger.class), bi);
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(Double.valueOf(123.6), BigInteger.class), bi);
-
-        assertTrue(BeansWrapper.forceUnwrappedNumberToType(n, Number.class) == n);
-        assertNull(BeansWrapper.forceUnwrappedNumberToType(n, RationalNumber.class));
-        RationalNumber r = new RationalNumber(1, 2);
-        assertTrue(BeansWrapper.forceUnwrappedNumberToType(r, RationalNumber.class) == r);
-        assertTrue(BeansWrapper.forceUnwrappedNumberToType(r, Number.class) == r);
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(r, Double.class), Double.valueOf(0.5));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(r, BigDecimal.class), new BigDecimal("0.5"));
-        assertEquals(BeansWrapper.forceUnwrappedNumberToType(r, BigInteger.class), BigInteger.ZERO);
-    }
-    
-    @SuppressWarnings("boxing")
-    public void testForceNumberArgumentsToParameterTypes() {
-        OverloadedMethodsSubset oms = new OverloadedFixArgsMethods();
-        Class[] paramTypes = new Class[] { Short.TYPE, Short.class, Double.TYPE, BigDecimal.class, BigInteger.class };
-        Object[] args;
-        
-        args = newArgs();
-        oms.forceNumberArgumentsToParameterTypes(args, paramTypes, new int[] { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF });
-        Assert.assertArrayEquals(
-                args,
-                new Object[] { (short) 123, (short) 123, 123.75, new BigDecimal("123.75"), BigInteger.valueOf(123) });
-        
-        args = newArgs();
-        oms.forceNumberArgumentsToParameterTypes(args, paramTypes, new int[] { 0, 0, 0, 0, 0 });
-        Assert.assertArrayEquals(args, newArgs());
-        
-        args = newArgs();
-        oms.forceNumberArgumentsToParameterTypes(args, paramTypes, new int[] { 8, 8, 8, 8, 8 });
-        Assert.assertArrayEquals(args, newArgs());
-        
-        args = newArgs();
-        oms.forceNumberArgumentsToParameterTypes(args, paramTypes, new int[] { 0xFFFF, 0, 0xFFFF, 0, 0xFFFF });
-        Assert.assertArrayEquals(
-                args,
-                new Object[] { (short) 123, 123.75, 123.75, 123.75, BigInteger.valueOf(123) });
-    }
-
-    @SuppressWarnings("boxing")
-    private Object[] newArgs() {
-        return new Object[] { 123.75, 123.75, 123.75, 123.75, 123.75 };
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/ModelCacheTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/ModelCacheTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/ModelCacheTest.java
deleted file mode 100644
index 7d4a020..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/ModelCacheTest.java
+++ /dev/null
@@ -1,65 +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.beans;
-
-import static org.junit.Assert.*;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class ModelCacheTest {
-    
-    @Test
-    public void modelCacheOff() throws Exception {
-        BeansWrapper bw = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
-        assertFalse(bw.getUseModelCache());  // default is off
-        
-        String s = "foo";
-        assertNotSame(bw.wrap(s), bw.wrap(s));
-        
-        C c = new C();
-        assertNotSame(bw.wrap(c), bw.wrap(c));
-    }
-    
-    @Test
-    public void modelCacheOn() throws Exception {
-        BeansWrapper bw = new BeansWrapper(Configuration.VERSION_3_0_0);
-        bw.setUseModelCache(true);
-        assertTrue(bw.getUseModelCache());
-        
-        String s = "foo";
-        assertSame(bw.wrap(s), bw.wrap(s));
-        
-        C c = new C();
-        TemplateModel wrappedC = bw.wrap(c);
-        assertSame(wrappedC, bw.wrap(c));
-        
-        bw.clearClassIntrospecitonCache();
-        assertNotSame(wrappedC, bw.wrap(c));
-        assertSame(bw.wrap(c), bw.wrap(c));
-    }
-
-    static public class C { }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/OverloadedNumberUtilTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/OverloadedNumberUtilTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/OverloadedNumberUtilTest.java
deleted file mode 100644
index de5cc1d..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/OverloadedNumberUtilTest.java
+++ /dev/null
@@ -1,585 +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.beans;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-import junit.framework.TestCase;
-
-@SuppressWarnings("boxing")
-public class OverloadedNumberUtilTest extends TestCase {
-
-    public OverloadedNumberUtilTest(String name) {
-        super(name);
-    }
-    
-    public void testIntegerCoercions() {
-        cipEqu(Byte.valueOf(Byte.MAX_VALUE));
-        cipEqu(Byte.valueOf((byte) 0));
-        cipEqu(Byte.valueOf(Byte.MIN_VALUE));
-        
-        cipEqu(Short.valueOf(Byte.MAX_VALUE),
-                new OverloadedNumberUtil.ShortOrByte((short) Byte.MAX_VALUE, Byte.MAX_VALUE));
-        cipEqu(Short.valueOf((short) 0),
-                new OverloadedNumberUtil.ShortOrByte((short) 0, (byte) 0));
-        cipEqu(Short.valueOf(Byte.MIN_VALUE),
-                new OverloadedNumberUtil.ShortOrByte((short) Byte.MIN_VALUE, Byte.MIN_VALUE));
-        
-        cipEqu(Short.valueOf((short) (Byte.MAX_VALUE + 1)));
-        cipEqu(Short.valueOf((short) (Byte.MIN_VALUE - 1)));
-        cipEqu(Short.valueOf(Short.MAX_VALUE));
-        cipEqu(Short.valueOf(Short.MIN_VALUE));
-        
-        cipEqu(Integer.valueOf(Byte.MAX_VALUE),
-                new OverloadedNumberUtil.IntegerOrByte((int) Byte.MAX_VALUE, Byte.MAX_VALUE));
-        cipEqu(Integer.valueOf(0),
-                new OverloadedNumberUtil.IntegerOrByte(0, (byte) 0));
-        cipEqu(Integer.valueOf(Byte.MIN_VALUE),
-                new OverloadedNumberUtil.IntegerOrByte((int) Byte.MIN_VALUE, Byte.MIN_VALUE));
-        
-        cipEqu(Integer.valueOf(Byte.MAX_VALUE + 1),
-                new OverloadedNumberUtil.IntegerOrShort(Byte.MAX_VALUE + 1, (short) (Byte.MAX_VALUE + 1)));
-        cipEqu(Integer.valueOf(Byte.MIN_VALUE - 1),
-                new OverloadedNumberUtil.IntegerOrShort(Byte.MIN_VALUE - 1, (short) (Byte.MIN_VALUE - 1)));
-        cipEqu(Integer.valueOf(Short.MAX_VALUE),
-                new OverloadedNumberUtil.IntegerOrShort((int) Short.MAX_VALUE, Short.MAX_VALUE));
-        cipEqu(Integer.valueOf(Short.MIN_VALUE),
-                new OverloadedNumberUtil.IntegerOrShort((int) Short.MIN_VALUE, Short.MIN_VALUE));
-        
-        cipEqu(Integer.valueOf(Short.MAX_VALUE + 1));
-        cipEqu(Integer.valueOf(Short.MIN_VALUE - 1));
-        cipEqu(Integer.valueOf(Integer.MAX_VALUE));
-        cipEqu(Integer.valueOf(Integer.MIN_VALUE));
-        
-        cipEqu(Long.valueOf(Byte.MAX_VALUE),
-                new OverloadedNumberUtil.LongOrByte((long) Byte.MAX_VALUE, Byte.MAX_VALUE));
-        cipEqu(Long.valueOf(0),
-                new OverloadedNumberUtil.LongOrByte((long) 0, (byte) 0));
-        cipEqu(Long.valueOf(Byte.MIN_VALUE),
-                new OverloadedNumberUtil.LongOrByte((long) Byte.MIN_VALUE, Byte.MIN_VALUE));
-        
-        cipEqu(Long.valueOf(Byte.MAX_VALUE + 1),
-                new OverloadedNumberUtil.LongOrShort((long) (Byte.MAX_VALUE + 1), (short) (Byte.MAX_VALUE + 1)));
-        cipEqu(Long.valueOf(Byte.MIN_VALUE - 1),
-                new OverloadedNumberUtil.LongOrShort((long) (Byte.MIN_VALUE - 1), (short) (Byte.MIN_VALUE - 1)));
-        cipEqu(Long.valueOf(Short.MAX_VALUE),
-                new OverloadedNumberUtil.LongOrShort((long) Short.MAX_VALUE, Short.MAX_VALUE));
-        cipEqu(Long.valueOf(Short.MIN_VALUE),
-                new OverloadedNumberUtil.LongOrShort((long) Short.MIN_VALUE, Short.MIN_VALUE));
-
-        cipEqu(Long.valueOf(Short.MAX_VALUE + 1),
-                new OverloadedNumberUtil.LongOrInteger((long) Short.MAX_VALUE + 1, Short.MAX_VALUE + 1));
-        cipEqu(Long.valueOf(Short.MIN_VALUE - 1),
-                new OverloadedNumberUtil.LongOrInteger((long) Short.MIN_VALUE - 1, Short.MIN_VALUE - 1));
-        cipEqu(Long.valueOf(Integer.MAX_VALUE),
-                new OverloadedNumberUtil.LongOrInteger((long) Integer.MAX_VALUE, Integer.MAX_VALUE));
-        cipEqu(Long.valueOf(Integer.MIN_VALUE),
-                new OverloadedNumberUtil.LongOrInteger((long) Integer.MIN_VALUE, Integer.MIN_VALUE));
-        
-        cipEqu(Long.valueOf(Integer.MAX_VALUE + 1L));
-        cipEqu(Long.valueOf(Integer.MIN_VALUE - 1L));
-        cipEqu(Long.valueOf(Long.MAX_VALUE));
-        cipEqu(Long.valueOf(Long.MIN_VALUE));
-    }
-    
-    public void testIntegerNoCoercions() {
-        cipEqu(Integer.valueOf(Byte.MAX_VALUE), Integer.valueOf(Byte.MAX_VALUE), 0);
-        cipEqu(Integer.valueOf(0), Integer.valueOf(0), 0);
-        cipEqu(Integer.valueOf(Byte.MIN_VALUE), Integer.valueOf(Byte.MIN_VALUE), 0);
-    }
-    
-    public void testIntegerLimitedCoercions() {
-        cipEqu(Integer.valueOf(Byte.MAX_VALUE), Integer.valueOf(Byte.MAX_VALUE), TypeFlags.INTEGER);
-        cipEqu(Integer.valueOf(0), Integer.valueOf(0), TypeFlags.INTEGER);
-        cipEqu(Integer.valueOf(Byte.MIN_VALUE), Integer.valueOf(Byte.MIN_VALUE), TypeFlags.INTEGER);
-        
-        cipEqu(Long.valueOf(Integer.MAX_VALUE + 1L), Long.valueOf(Integer.MAX_VALUE + 1L), TypeFlags.INTEGER);
-        
-        for (int n = -1; n < 2; n++) {
-            final Long longN = Long.valueOf(n);
-            cipEqu(longN, new OverloadedNumberUtil.LongOrInteger(longN, n), TypeFlags.INTEGER);
-            cipEqu(longN, new OverloadedNumberUtil.LongOrShort(longN, (short) n), TypeFlags.SHORT);
-            cipEqu(longN, new OverloadedNumberUtil.LongOrByte(longN, (byte) n), TypeFlags.BYTE);
-            cipEqu(longN, new OverloadedNumberUtil.LongOrShort(longN, (short) n),
-                    TypeFlags.SHORT | TypeFlags.INTEGER);
-        }
-    }
-
-    public void testBigDecimalCoercions() {
-        cipEqu(new BigDecimal(123), new OverloadedNumberUtil.IntegerBigDecimal(new BigDecimal(123)));
-        cipEqu(new BigDecimal(123), new OverloadedNumberUtil.IntegerBigDecimal(new BigDecimal(123)),
-                TypeFlags.DOUBLE | TypeFlags.INTEGER);
-        cipEqu(new BigDecimal(123), TypeFlags.INTEGER);
-        cipEqu(new BigDecimal(123), TypeFlags.INTEGER | TypeFlags.LONG);
-        cipEqu(new BigDecimal(123), TypeFlags.DOUBLE);
-        cipEqu(new BigDecimal(123), TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        
-        cipEqu(new BigDecimal(123.5));
-        // Not wasting time with check if it's a whole number if we only have integer-only or non-integer-only targets:  
-        cipEqu(new BigDecimal(123.5), TypeFlags.INTEGER | TypeFlags.LONG);
-        cipEqu(new BigDecimal(123.5), TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        
-        cipEqu(new BigDecimal("0.01"));
-        cipEqu(new BigDecimal("-0.01"));
-        cipEqu(BigDecimal.ZERO, new OverloadedNumberUtil.IntegerBigDecimal(BigDecimal.ZERO));
-    }
-
-    public void testUnknownNumberCoercion() {
-        cipEqu(new RationalNumber(2, 3));
-    }
-
-    @SuppressWarnings("boxing")
-    public void testDoubleCoercion() {
-        cipEqu(Double.valueOf(1.5), new OverloadedNumberUtil.DoubleOrFloat(1.5));
-        cipEqu(Double.valueOf(-0.125), new OverloadedNumberUtil.DoubleOrFloat(-0.125));
-        cipEqu(Double.valueOf(Float.MAX_VALUE), new OverloadedNumberUtil.DoubleOrFloat((double) Float.MAX_VALUE));
-        cipEqu(Double.valueOf(-Float.MAX_VALUE), new OverloadedNumberUtil.DoubleOrFloat((double) -Float.MAX_VALUE));
-        cipEqu(Double.valueOf(Float.MAX_VALUE * 10.0));
-        cipEqu(Double.valueOf(-Float.MAX_VALUE * 10.0));
-        
-        cipEqu(Double.valueOf(0), new OverloadedNumberUtil.DoubleOrByte(0.0, (byte) 0));
-        cipEqu(Double.valueOf(Byte.MAX_VALUE), new OverloadedNumberUtil.DoubleOrByte((double) Byte.MAX_VALUE, Byte.MAX_VALUE));
-        cipEqu(Double.valueOf(Byte.MIN_VALUE), new OverloadedNumberUtil.DoubleOrByte((double) Byte.MIN_VALUE, Byte.MIN_VALUE));
-        
-        cipEqu(Double.valueOf(Byte.MAX_VALUE + 1), new OverloadedNumberUtil.DoubleOrShort((double)
-                (Byte.MAX_VALUE + 1), (short) (Byte.MAX_VALUE + 1)));
-        cipEqu(Double.valueOf(Byte.MIN_VALUE - 1), new OverloadedNumberUtil.DoubleOrShort((double)
-                (Byte.MIN_VALUE - 1), (short) (Byte.MIN_VALUE - 1)));
-        
-        cipEqu(Double.valueOf(Short.MAX_VALUE + 1),
-                new OverloadedNumberUtil.DoubleOrIntegerOrFloat((double) Short.MAX_VALUE + 1, Short.MAX_VALUE + 1));
-        cipEqu(Double.valueOf(Short.MIN_VALUE - 1),
-                new OverloadedNumberUtil.DoubleOrIntegerOrFloat((double) Short.MIN_VALUE - 1, Short.MIN_VALUE -  1));
-        cipEqu(Double.valueOf(16777216), new OverloadedNumberUtil.DoubleOrIntegerOrFloat(16777216.0, 16777216));
-        cipEqu(Double.valueOf(-16777216), new OverloadedNumberUtil.DoubleOrIntegerOrFloat(-16777216.0, -16777216));
-        
-        cipEqu(Double.valueOf(Integer.MAX_VALUE),
-                new OverloadedNumberUtil.DoubleOrInteger((double) Integer.MAX_VALUE, Integer.MAX_VALUE));
-        cipEqu(Double.valueOf(Integer.MIN_VALUE),
-                new OverloadedNumberUtil.DoubleOrInteger((double) Integer.MIN_VALUE, Integer.MIN_VALUE));
-        
-        cipEqu(Double.valueOf(Integer.MAX_VALUE + 1L),
-                new OverloadedNumberUtil.DoubleOrLong(Double.valueOf(Integer.MAX_VALUE + 1L), Integer.MAX_VALUE + 1L));
-        cipEqu(Double.valueOf(Integer.MIN_VALUE - 1L),
-                new OverloadedNumberUtil.DoubleOrLong(Double.valueOf(Integer.MIN_VALUE - 1L), Integer.MIN_VALUE - 1L));
-        cipEqu(Double.valueOf(Long.MAX_VALUE),
-                new OverloadedNumberUtil.DoubleOrFloat((double) Long.MAX_VALUE));
-        cipEqu(Double.valueOf(Long.MIN_VALUE),
-                new OverloadedNumberUtil.DoubleOrFloat((double) Long.MIN_VALUE));
-
-        // When only certain target types are present:
-        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrByte(5.0, (byte) 5), TypeFlags.BYTE);
-        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrByte(5.0, (byte) 5), TypeFlags.BYTE | TypeFlags.SHORT);
-        cipEqu(Double.valueOf(-129), TypeFlags.BYTE);
-        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrShort(5.0, (short) 5), TypeFlags.SHORT);
-        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrInteger(5.0, 5), TypeFlags.INTEGER);
-        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrLong(5.0, 5), TypeFlags.LONG);
-        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrFloat(5.0), TypeFlags.FLOAT);
-        cipEqu(Double.valueOf(5), Double.valueOf(5), TypeFlags.DOUBLE);
-        cipEqu(Double.valueOf(5), new OverloadedNumberUtil.DoubleOrFloat(5.0),
-                TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(Double.valueOf(5.9), new OverloadedNumberUtil.DoubleOrFloat(5.9),
-                TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(Double.valueOf(5.9),
-                TypeFlags.DOUBLE | TypeFlags.INTEGER);
-        cipEqu(Double.valueOf(5.9), new OverloadedNumberUtil.DoubleOrFloat(5.9),
-                TypeFlags.FLOAT | TypeFlags.INTEGER);
-        cipEqu(Double.valueOf(5.9), TypeFlags.INTEGER);
-        cipEqu(Double.valueOf(Long.MAX_VALUE),
-                new OverloadedNumberUtil.DoubleOrFloat((double) Long.MAX_VALUE),
-                TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(Double.valueOf(Long.MAX_VALUE),
-                TypeFlags.DOUBLE | TypeFlags.LONG);
-        cipEqu(Double.valueOf(Long.MIN_VALUE),
-                new OverloadedNumberUtil.DoubleOrFloat((double) Long.MIN_VALUE),
-                TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(Double.valueOf(Float.MAX_VALUE * 10),
-                TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(Double.valueOf(-Float.MAX_VALUE * 10),
-                TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        
-        // Rounded values:
-        cipEqu(Double.valueOf(0.0000009),
-                new OverloadedNumberUtil.DoubleOrByte(0.0000009, (byte) 0));
-        cipEqu(Double.valueOf(-0.0000009),
-                new OverloadedNumberUtil.DoubleOrByte(-0.0000009, (byte) 0));
-        cipEqu(Double.valueOf(0.9999991),
-                new OverloadedNumberUtil.DoubleOrByte(0.9999991, (byte) 1));
-        cipEqu(Double.valueOf(-0.9999991),
-                new OverloadedNumberUtil.DoubleOrByte(-0.9999991, (byte) -1));
-        cipEqu(Double.valueOf(0.0000009),
-                new OverloadedNumberUtil.DoubleOrShort(0.0000009, (short) 0),
-                TypeFlags.SHORT | TypeFlags.DOUBLE);
-        cipEqu(Double.valueOf(0.0000009), new OverloadedNumberUtil.DoubleOrInteger(0.0000009, 0),
-                TypeFlags.INTEGER | TypeFlags.DOUBLE);
-        cipEqu(Double.valueOf(0.0000009), new OverloadedNumberUtil.DoubleOrLong(0.0000009, 0),
-                TypeFlags.LONG | TypeFlags.DOUBLE);
-        cipEqu(Double.valueOf(0.0000009),
-                new OverloadedNumberUtil.DoubleOrByte(0.0000009, (byte) 0), TypeFlags.BYTE);
-        cipEqu(Double.valueOf(0.0000009),
-                new OverloadedNumberUtil.DoubleOrShort(0.0000009, (short) 0), TypeFlags.SHORT);
-        cipEqu(Double.valueOf(0.0000009),
-                new OverloadedNumberUtil.DoubleOrInteger(0.0000009, 0), TypeFlags.INTEGER);
-        cipEqu(Double.valueOf(0.0000009),
-                new OverloadedNumberUtil.DoubleOrLong(0.0000009, 0L), TypeFlags.LONG);
-        cipEqu(Double.valueOf(0.9999999),
-                new OverloadedNumberUtil.DoubleOrInteger(0.9999999, 1), TypeFlags.INTEGER);
-        cipEqu(Double.valueOf(Byte.MAX_VALUE + 0.9e-6),
-                new OverloadedNumberUtil.DoubleOrByte(Byte.MAX_VALUE + 0.9e-6, Byte.MAX_VALUE));
-        cipEqu(Double.valueOf(Byte.MIN_VALUE - 0.9e-6),
-                new OverloadedNumberUtil.DoubleOrByte(Byte.MIN_VALUE - 0.9e-6, Byte.MIN_VALUE));
-        cipEqu(Double.valueOf(Byte.MAX_VALUE + 1.1e-6),
-                new OverloadedNumberUtil.DoubleOrFloat(Byte.MAX_VALUE + 1.1e-6));
-        cipEqu(Double.valueOf(Byte.MIN_VALUE - 1.1e-6),
-                new OverloadedNumberUtil.DoubleOrFloat(Byte.MIN_VALUE - 1.1e-6));
-        cipEqu(Double.valueOf(Byte.MAX_VALUE + 0.9999991),
-                new OverloadedNumberUtil.DoubleOrShort(
-                        Byte.MAX_VALUE + 0.9999991, (short) (Byte.MAX_VALUE + 1)));
-        cipEqu(Double.valueOf(Byte.MIN_VALUE - 0.9999991),
-                new OverloadedNumberUtil.DoubleOrShort(
-                        Byte.MIN_VALUE - 0.9999991, (short) (Byte.MIN_VALUE - 1)));
-        
-        cipEqu(Double.valueOf(Byte.MAX_VALUE + 1), new OverloadedNumberUtil.DoubleOrShort((double)
-                (Byte.MAX_VALUE + 1), (short) (Byte.MAX_VALUE + 1)));
-        cipEqu(Double.valueOf(Byte.MIN_VALUE - 1), new OverloadedNumberUtil.DoubleOrShort((double)
-                (Byte.MIN_VALUE - 1), (short) (Byte.MIN_VALUE - 1)));
-        
-        cipEqu(Short.MAX_VALUE + 0.9999991,
-                new OverloadedNumberUtil.DoubleOrIntegerOrFloat(Short.MAX_VALUE + 0.9999991, Short.MAX_VALUE + 1));
-        cipEqu(Short.MIN_VALUE - 0.9999991,
-                new OverloadedNumberUtil.DoubleOrIntegerOrFloat(Short.MIN_VALUE - 0.9999991, Short.MIN_VALUE - 1));
-        cipEqu(16777216 + 0.9e-6,
-                new OverloadedNumberUtil.DoubleOrIntegerOrFloat(16777216 + 0.9e-6, 16777216));
-        cipEqu(-16777216 - 0.9e-6,
-                new OverloadedNumberUtil.DoubleOrIntegerOrFloat(-16777216 - 0.9e-6, -16777216));
-        
-        cipEqu(Integer.MAX_VALUE + 0.9e-6,
-                new OverloadedNumberUtil.DoubleOrInteger(Integer.MAX_VALUE + 0.9e-6, Integer.MAX_VALUE));
-        cipEqu(Integer.MIN_VALUE - 0.9e-6,
-                new OverloadedNumberUtil.DoubleOrInteger(Integer.MIN_VALUE - 0.9e-6, Integer.MIN_VALUE));
-        
-        cipEqu(Integer.MAX_VALUE + 1L + 0.9e-6,
-                new OverloadedNumberUtil.DoubleOrFloat(Integer.MAX_VALUE + 1L + 0.9e-6));
-        cipEqu(Integer.MIN_VALUE - 1L - 0.9e-6,
-                new OverloadedNumberUtil.DoubleOrFloat(Integer.MIN_VALUE - 1L - 0.9e-6));
-        cipEqu(Long.MAX_VALUE + 0.9e-6,
-                new OverloadedNumberUtil.DoubleOrFloat(Long.MAX_VALUE + 0.9e-6));
-        cipEqu(Long.MIN_VALUE - 0.9e-6,
-                new OverloadedNumberUtil.DoubleOrFloat(Long.MIN_VALUE - 0.9e-6));
-    }
-
-    @SuppressWarnings("boxing")
-    public void testFloatCoercion() {
-        cipEqu(1.00002f);
-        cipEqu(-1.00002f);
-        cipEqu(1.999989f);
-        cipEqu(-1.999989f);
-        cipEqu(16777218f);
-        cipEqu(-16777218f);
-        
-        cipEqu(1f, new OverloadedNumberUtil.FloatOrByte(1f, (byte) 1));
-        cipEqu(-1f, new OverloadedNumberUtil.FloatOrByte(-1f, (byte) -1));
-        cipEqu(1.000009f, new OverloadedNumberUtil.FloatOrByte(1.000009f, (byte) 1));
-        cipEqu(-1.000009f, new OverloadedNumberUtil.FloatOrByte(-1.000009f, (byte) -1));
-        cipEqu(1.999991f, new OverloadedNumberUtil.FloatOrByte(1.999991f, (byte) 2));
-        cipEqu(-1.999991f, new OverloadedNumberUtil.FloatOrByte(-1.999991f, (byte) -2));
-        
-        cipEqu(1000f, new OverloadedNumberUtil.FloatOrShort(1000f, (short) 1000));
-        cipEqu(-1000f, new OverloadedNumberUtil.FloatOrShort(-1000f, (short) -1000));
-        cipEqu(1000.00006f);
-
-        cipEqu(60000f, new OverloadedNumberUtil.FloatOrInteger(60000f, 60000));
-        cipEqu(-60000f, new OverloadedNumberUtil.FloatOrInteger(-60000f, -60000));
-        cipEqu(60000.004f);
-
-        cipEqu(100f, new OverloadedNumberUtil.FloatOrByte(100f, (byte) 100), TypeFlags.MASK_KNOWN_INTEGERS);
-        cipEqu(1000f, new OverloadedNumberUtil.FloatOrShort(1000f, (short) 1000), TypeFlags.MASK_KNOWN_INTEGERS);
-        cipEqu(60000f, new OverloadedNumberUtil.FloatOrInteger(60000f, 60000), TypeFlags.MASK_KNOWN_INTEGERS);
-        cipEqu(60000f, new OverloadedNumberUtil.FloatOrInteger(60000f, 60000), TypeFlags.LONG);
-        cipEqu((float) Integer.MAX_VALUE, (float) Integer.MAX_VALUE, TypeFlags.LONG);
-        cipEqu((float) -Integer.MAX_VALUE, (float) -Integer.MAX_VALUE);
-        
-        cipEqu(0.5f, 0.5f, TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(0.5f, 0.5f, TypeFlags.DOUBLE);
-    }
-    
-    public void testBigIntegerCoercion() {
-        BigInteger bi;
-        
-        cipEqu(BigInteger.ZERO, new OverloadedNumberUtil.BigIntegerOrByte(BigInteger.ZERO));
-        bi = new BigInteger(String.valueOf(Byte.MAX_VALUE));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrByte(bi));
-        bi = new BigInteger(String.valueOf(Byte.MIN_VALUE));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrByte(bi));
-        
-        bi = new BigInteger(String.valueOf(Byte.MAX_VALUE + 1));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrShort(bi));
-        bi = new BigInteger(String.valueOf(Byte.MIN_VALUE - 1));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrShort(bi));
-        bi = new BigInteger(String.valueOf(Short.MAX_VALUE));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrShort(bi));
-        bi = new BigInteger(String.valueOf(Short.MIN_VALUE));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrShort(bi));
-        
-        bi = new BigInteger(String.valueOf(Short.MAX_VALUE + 1));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrInteger(bi));
-        bi = new BigInteger(String.valueOf(Short.MIN_VALUE - 1));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrInteger(bi));
-        bi = new BigInteger(String.valueOf(Integer.MAX_VALUE));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrInteger(bi));
-        bi = new BigInteger(String.valueOf(Integer.MIN_VALUE));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrInteger(bi));
-        
-        bi = new BigInteger(String.valueOf(Integer.MAX_VALUE + 1L));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrLong(bi));
-        bi = new BigInteger(String.valueOf(Integer.MIN_VALUE - 1L));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrLong(bi));
-        bi = new BigInteger(String.valueOf(Long.MAX_VALUE));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrLong(bi));
-        bi = new BigInteger(String.valueOf(Long.MIN_VALUE));
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrLong(bi));
-        
-        cipEqu(new BigInteger(String.valueOf(Long.MAX_VALUE)).add(BigInteger.ONE));
-        cipEqu(new BigInteger(String.valueOf(Long.MIN_VALUE)).subtract(BigInteger.ONE));
-        
-        bi = new BigInteger("0");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi),
-                TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi),
-                TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi),
-                TypeFlags.FLOAT);
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi),
-                TypeFlags.DOUBLE);
-
-        bi = new BigInteger("16777215");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        bi = new BigInteger("-16777215");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        
-        bi = new BigInteger("16777216");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        bi = new BigInteger("-16777216");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrFloat(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        
-        bi = new BigInteger("16777217");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, TypeFlags.FLOAT);
-        bi = new BigInteger("-16777217");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, TypeFlags.FLOAT);
-        
-        bi = new BigInteger("9007199254740991");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.DOUBLE);
-        bi = new BigInteger("-9007199254740991");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.DOUBLE);
-        
-        bi = new BigInteger("9007199254740992");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.DOUBLE);
-        bi = new BigInteger("-9007199254740992");
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, new OverloadedNumberUtil.BigIntegerOrDouble(bi), TypeFlags.DOUBLE);
-        
-        bi = new BigInteger("9007199254740993");
-        cipEqu(bi, TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(bi, TypeFlags.FLOAT);
-        cipEqu(bi, TypeFlags.DOUBLE);
-        bi = new BigInteger("-9007199254740993");
-        cipEqu(bi, TypeFlags.MASK_KNOWN_NONINTEGERS);
-        cipEqu(bi, TypeFlags.DOUBLE | TypeFlags.FLOAT);
-        cipEqu(bi, TypeFlags.FLOAT);
-        cipEqu(bi, TypeFlags.DOUBLE);
-        
-        bi = new BigInteger("9007199254740994");
-        cipEqu(bi, TypeFlags.MASK_KNOWN_NONINTEGERS);
-        bi = new BigInteger("-9007199254740994");
-        cipEqu(bi, TypeFlags.MASK_KNOWN_NONINTEGERS);
-    }
-    
-    private void cipEqu(Number actualAndExpected) {
-        cipEqu(actualAndExpected, actualAndExpected, -1);
-    }
-    
-    private void cipEqu(Number actual, Number expected) {
-        cipEqu(actual, expected, -1);
-    }
-
-    private void cipEqu(Number actualAndExpected, int flags) {
-        cipEqu(actualAndExpected, actualAndExpected, flags);
-    }
-    
-    @SuppressWarnings("boxing")
-    private void cipEqu(Number actual, Number expected, int flags) {
-        Number res = OverloadedNumberUtil.addFallbackType(actual, flags);
-        assertEquals(expected.getClass(), res.getClass());
-        assertEquals(expected, res);
-        
-        // Some number types wrap the number with multiple types and equals() only compares one of them. So we try to
-        // catch any inconsistency:
-        assertEquals(expected.byteValue(), res.byteValue());
-        assertEquals(expected.shortValue(), res.shortValue());
-        assertEquals(expected.intValue(), res.intValue());
-        assertEquals(expected.longValue(), res.longValue());
-        assertEquals(expected.floatValue(), res.floatValue());
-        assertEquals(expected.doubleValue(), res.doubleValue());
-    }
-    
-    public void testGetArgumentConversionPrice() {
-        // Unknown number types:
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                RationalNumber.class, Integer.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, RationalNumber.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                RationalNumber.class, Float.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                Float.class, RationalNumber.class));
-        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
-                RationalNumber.class, RationalNumber.class));
-        
-        // Fully check some rows (not all of them; the code is generated anyways):
-
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, Byte.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, Short.class));
-        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, Integer.class));
-        assertEquals(10004, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, Long.class));
-        assertEquals(10005, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, BigInteger.class));
-        assertEquals(30006, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, Float.class));
-        assertEquals(20007, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, Double.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, BigDecimal.class));
-        
-        assertEquals(45001, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, Byte.class));
-        assertEquals(44002, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, Short.class));
-        assertEquals(41003, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, Integer.class));
-        assertEquals(41004, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, Long.class));
-        assertEquals(40005, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, BigInteger.class));
-        assertEquals(33006, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, Float.class));
-        assertEquals(32007, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, Double.class));
-        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, BigDecimal.class));
-        
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, Byte.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, Short.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, Integer.class));
-        assertEquals(21004, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, Long.class));
-        assertEquals(21005, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, BigInteger.class));
-        assertEquals(40006, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, Float.class));
-        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, Double.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, BigDecimal.class));
-        
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, Byte.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, Short.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, Integer.class));
-        assertEquals(Integer.MAX_VALUE, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, Long.class));
-        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, BigInteger.class));
-        assertEquals(40006, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, Float.class));
-        assertEquals(20007, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, Double.class));
-        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, BigDecimal.class));
-        
-        // Check if all fromC is present:
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                Byte.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                Short.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                Integer.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                Long.class, BigDecimal.class));
-        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigInteger.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                Float.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                Double.class, BigDecimal.class));
-        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
-                BigDecimal.class, BigDecimal.class));
-        assertEquals(0, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.IntegerBigDecimal.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrFloat.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.FloatOrByte.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrShort.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.FloatOrByte.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.FloatOrShort.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.FloatOrInteger.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrByte.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrIntegerOrFloat.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrInteger.class, BigDecimal.class));
-        assertEquals(20008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.DoubleOrLong.class, BigDecimal.class));
-        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrByte.class, BigDecimal.class));
-        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrShort.class, BigDecimal.class));
-        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrInteger.class, BigDecimal.class));
-        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrLong.class, BigDecimal.class));
-        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrFloat.class, BigDecimal.class));
-        assertEquals(10008, OverloadedNumberUtil.getArgumentConversionPrice(
-                OverloadedNumberUtil.BigIntegerOrDouble.class, BigDecimal.class));
-    }
-    
-}


[02/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/ParameterListPreferabilityTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/ParameterListPreferabilityTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/ParameterListPreferabilityTest.java
deleted file mode 100644
index 60c07ef..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/ParameterListPreferabilityTest.java
+++ /dev/null
@@ -1,445 +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.beans;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.freemarker.core.util._NumberUtil;
-
-import junit.framework.TestCase;
-
-@SuppressWarnings("boxing")
-public class ParameterListPreferabilityTest extends TestCase {
-
-    public ParameterListPreferabilityTest(String name) {
-        super(name);
-    }
-    
-    public void testNumberical() {
-        // Note: the signature lists consists of the same elements, only their order changes depending on the type
-        // of the argument value.
-        
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { byte.class },
-                    new Class[] { Byte.class },
-                    new Class[] { short.class },
-                    new Class[] { Short.class },
-                    new Class[] { int.class },
-                    new Class[] { Integer.class },
-                    new Class[] { long.class },
-                    new Class[] { Long.class },
-                    new Class[] { BigInteger.class },
-                    new Class[] { float.class },
-                    new Class[] { Float.class },
-                    new Class[] { double.class },
-                    new Class[] { Double.class },
-                    new Class[] { BigDecimal.class },
-                    new Class[] { Number.class },
-                    new Class[] { Serializable.class },
-                    new Class[] { Object.class }
-                },
-                new Object[] { (byte) 1 });
-        
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { BigDecimal.class },
-                    new Class[] { BigInteger.class },
-                    new Class[] { int.class },
-                    new Class[] { Integer.class },
-                    new Class[] { long.class },
-                    new Class[] { Long.class },
-                    new Class[] { double.class },
-                    new Class[] { Double.class },
-                    new Class[] { float.class },
-                    new Class[] { Float.class },
-                    new Class[] { short.class },
-                    new Class[] { Short.class },
-                    new Class[] { byte.class },
-                    new Class[] { Byte.class },
-                    new Class[] { Number.class },
-                    new Class[] { Serializable.class },
-                    new Class[] { Object.class },
-                },
-                new Object[] { new OverloadedNumberUtil.IntegerBigDecimal(new BigDecimal("1")) });
-
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { BigDecimal.class },
-                    new Class[] { double.class },
-                    new Class[] { Double.class },
-                    new Class[] { float.class },
-                    new Class[] { Float.class },
-                    new Class[] { BigInteger.class },
-                    new Class[] { int.class },
-                    new Class[] { Integer.class },
-                    new Class[] { long.class },
-                    new Class[] { Long.class },
-                    new Class[] { short.class },
-                    new Class[] { Short.class },
-                    new Class[] { byte.class },
-                    new Class[] { Byte.class },
-                    new Class[] { Number.class },
-                    new Class[] { Serializable.class },
-                    new Class[] { Object.class },
-                },
-                new Object[] { new BigDecimal("1") /* possibly non-integer */ });
-        
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { float.class },
-                    new Class[] { Float.class },
-                    new Class[] { double.class },
-                    new Class[] { Double.class },
-                    new Class[] { BigDecimal.class },
-                    new Class[] { int.class },
-                    new Class[] { Integer.class },
-                    new Class[] { long.class },
-                    new Class[] { Long.class },
-                    new Class[] { short.class },
-                    new Class[] { Short.class },
-                    new Class[] { byte.class },
-                    new Class[] { Byte.class },
-                    new Class[] { BigInteger.class },
-                    new Class[] { Number.class },
-                    new Class[] { Serializable.class },
-                    new Class[] { Object.class },
-                },
-                new Object[] { new OverloadedNumberUtil.FloatOrByte(1f, (byte) 1) });
-        
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { int.class },
-                    new Class[] { Integer.class },
-                    new Class[] { long.class },
-                    new Class[] { Long.class },
-                    new Class[] { BigInteger.class },
-                    new Class[] { double.class },
-                    new Class[] { Double.class },
-                    new Class[] { BigDecimal.class },
-                    new Class[] { short.class },
-                    new Class[] { Short.class },
-                    new Class[] { float.class },
-                    new Class[] { Float.class },
-                    
-                    // Two incompatibles removed, would be removed in reality:
-                    new Class[] { byte.class },
-                    new Class[] { Byte.class },
-                    
-                    new Class[] { Number.class },
-                    new Class[] { Serializable.class },
-                    new Class[] { Object.class }
-                },
-                new Object[] { new OverloadedNumberUtil.IntegerOrShort(1, (short) 1) });
-        
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { long.class },
-                    new Class[] { Long.class },
-                    new Class[] { BigInteger.class },
-                    new Class[] { BigDecimal.class },
-                    new Class[] { double.class },
-                    new Class[] { Double.class },
-                    new Class[] { float.class },
-                    new Class[] { Float.class },
-                    // skip byte and short  as the would be equal with int (all invalid target types)
-                    new Class[] { int.class },  // In reality, this and Integer are removed as not-applicable overloads
-                    new Class[] { Integer.class },
-                    new Class[] { Number.class },
-                    new Class[] { Serializable.class },
-                    new Class[] { Object.class }
-                },
-                new Object[] { 1L });
-        
-        // Undecidable comparisons:
-        
-        testAllCmpPermutationsEqu(
-                new Class[][] {
-                        new Class[] { Byte.class },
-                        new Class[] { Short.class },
-                        new Class[] { Integer.class },
-                        new Class[] { Long.class },
-                        new Class[] { BigInteger.class },
-                        new Class[] { Float.class },
-                    },
-                    new Object[] { 1.0 });
-        
-        testAllCmpPermutationsEqu(
-                new Class[][] {
-                        new Class[] { byte.class },
-                        new Class[] { short.class },
-                        new Class[] { int.class },
-                        new Class[] { long.class },
-                        new Class[] { float.class },
-                    },
-                    new Object[] { 1.0 });
-    }
-    
-    public void testPrimitiveIsMoreSpecific() {
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { boolean.class },
-                    new Class[] { Boolean.class }
-                },
-                new Object[] { true });
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { char.class },
-                    new Class[] { Character.class }
-                },
-                new Object[] { 'x' });
-    }
-    
-    public void testCharIsMoreSpecificThanString() {
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { char.class },
-                    new Class[] { Character.class },
-                    new Class[] { String.class },
-                    new Class[] { CharSequence.class }
-                },
-                new Object[] { "s" });
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { char.class },
-                    new Class[] { Character.class },
-                    new Class[] { String.class }
-                },
-                new Object[] { 'c' });
-    }
-    
-    public void testClassHierarchy() {
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { LinkedHashMap.class },
-                    new Class[] { HashMap.class },
-                    new Class[] { Map.class },
-                    new Class[] { Object.class }
-                },
-                new Object[] { new LinkedHashMap() });
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { LinkedHashMap.class },
-                    new Class[] { Cloneable.class },
-                    new Class[] { Object.class }
-                },
-                new Object[] { new LinkedHashMap() });
-    }
-
-    public void testNumericalWithNonNumerical() {
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { int.class },
-                    new Class[] { Integer.class },
-                    new Class[] { Comparable.class },
-                    new Class[] { Object.class },
-                },
-                new Object[] { 1 });
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { int.class },
-                    new Class[] { Integer.class },
-                    new Class[] { char.class },
-                    new Class[] { Character.class },
-                },
-                new Object[] { 1 });
-    }
-    
-    public void testUnrelated() {
-        testAllCmpPermutationsEqu(
-                new Class[][] {
-                    new Class[] { Serializable.class },
-                    new Class[] { CharSequence.class },
-                    new Class[] { Comparable.class }
-                },
-                new Object[] { "s" });
-        
-        testAllCmpPermutationsEqu(
-                new Class[][] {
-                    new Class[] { HashMap.class },
-                    new Class[] { TreeMap.class }
-                },
-                new Object[] { null });
-    }
-
-    public void testMultiParameter() {
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { String.class, String.class, String.class },
-                        
-                    new Class[] { String.class, String.class, Object.class },
-                    new Class[] { String.class, Object.class, String.class },
-                    new Class[] { Object.class, String.class, String.class },
-                    
-                    new Class[] { String.class, Object.class, Object.class },
-                    new Class[] { Object.class, String.class, Object.class },
-                    new Class[] { Object.class, Object.class, String.class },
-                    
-                    new Class[] { Object.class, Object.class, Object.class },
-                },
-                new Object[] { "a", "b", "c" });
-        
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { String.class, String.class },
-                    new Class[] { String.class, Object.class },
-                    new Class[] { CharSequence.class, CharSequence.class },
-                    new Class[] { CharSequence.class, Object.class },
-                    new Class[] { Object.class, String.class },
-                    new Class[] { Object.class, CharSequence.class },
-                    new Class[] { Object.class, Object.class },
-                },
-                new Object[] { "a", "b" });
-        
-        // Subclassing is more important than primitive-VS-boxed:
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { boolean.class, boolean.class, boolean.class, String.class },
-                    new Class[] { Boolean.class, boolean.class, boolean.class, String.class },
-                    new Class[] { boolean.class, Boolean.class, Boolean.class, String.class },
-                    new Class[] { Boolean.class, boolean.class, Boolean.class, String.class },
-                    new Class[] { Boolean.class, Boolean.class, boolean.class, String.class },
-                    new Class[] { Boolean.class, Boolean.class, Boolean.class, String.class },
-                    new Class[] { Boolean.class, Boolean.class, Boolean.class, CharSequence.class },
-                    new Class[] { boolean.class, boolean.class, boolean.class, Object.class },
-                    new Class[] { Boolean.class, boolean.class, boolean.class, Object.class },
-                    new Class[] { boolean.class, Boolean.class, Boolean.class, Object.class },
-                    new Class[] { Boolean.class, boolean.class, Boolean.class, Object.class },
-                    new Class[] { Boolean.class, Boolean.class, boolean.class, Object.class },
-                    new Class[] { Boolean.class, Boolean.class, Boolean.class, Object.class },
-                },
-                new Object[] { true, false, true, "a" });
-        
-        // Subclassing is more important than primitive-VS-boxed:
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { int.class, int.class, int.class, String.class },
-                    new Class[] { Integer.class, int.class, int.class, String.class },
-                    new Class[] { int.class, Integer.class, Integer.class, String.class },
-                    new Class[] { Integer.class, int.class, Integer.class, String.class },
-                    new Class[] { Integer.class, Integer.class, int.class, String.class },
-                    new Class[] { Integer.class, Integer.class, Integer.class, String.class },
-                    new Class[] { Integer.class, Integer.class, Integer.class, CharSequence.class },
-                    new Class[] { int.class, int.class, int.class, Object.class },
-                    new Class[] { Integer.class, int.class, int.class, Object.class },
-                    new Class[] { int.class, Integer.class, Integer.class, Object.class },
-                    new Class[] { Integer.class, int.class, Integer.class, Object.class },
-                    new Class[] { Integer.class, Integer.class, int.class, Object.class },
-                    new Class[] { Integer.class, Integer.class, Integer.class, Object.class },
-                },
-                new Object[] { 1, 2, 3, "a" });
-    }
-
-    public void testVarargs() {
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { String.class, String[].class },
-                    new Class[] { String.class, CharSequence[].class },
-                    new Class[] { String.class, Object[].class },
-                },
-                new Object[] { "a", "b", "c" },
-                true);
-        
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { String.class, int[].class },
-                    new Class[] { String.class, Integer[].class },
-                    new Class[] { String.class, long[].class },
-                    new Class[] { String.class, Long[].class },
-                    new Class[] { String.class, double[].class },
-                    new Class[] { String.class, Double[].class },
-                    new Class[] { String.class, Serializable[].class },
-                    new Class[] { String.class, Object[].class },
-                },
-                new Object[] { "a", 1, 2, 3 },
-                true);
-        
-        // 0-long varargs list; in case of ambiguity, the varargs component type decides:
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { String.class, Object[].class },
-                    new Class[] { CharSequence.class, int[].class },
-                    new Class[] { CharSequence.class, Integer[].class },
-                    new Class[] { CharSequence.class, long[].class },
-                    new Class[] { CharSequence.class, Long[].class },
-                    new Class[] { CharSequence.class, double[].class },
-                    new Class[] { CharSequence.class, Double[].class },
-                    new Class[] { CharSequence.class, Serializable[].class },
-                    new Class[] { CharSequence.class, Object[].class },
-                    new Class[] { Object.class, int[].class },
-                },
-                new Object[] { "a" },
-                true);
-        
-        
-        // Different oms prefix length; in the case of ambiguity, the one with higher oms param count wins.
-        testAllCmpPermutationsInc(
-                new Class[][] {
-                    new Class[] { String.class, int.class, int.class, int[].class },
-                    new Class[] { String.class, int.class, int[].class },
-                    new Class[] { String.class, int[].class },
-                },
-                new Object[] { "a", 1, 2, 3 },
-                true);
-    }
-    
-    private void testAllCmpPermutationsInc(Class[][] sortedSignatures, Object[] args) {
-        testAllCmpPermutationsInc(sortedSignatures, args, false);
-    }
-
-    /**
-     * Compares all items with all other items in the provided descending sorted array of signatures, checking that
-     * for all valid indexes i and j, where j > i, it stands that sortedSignatures[i] > sortedSignatures[j].
-     * The comparisons are done with both operand orders, also each items is compared to itself too.
-     * 
-     * @param sortedSignatures method signatures sorted by decreasing specificity
-     */
-    private void testAllCmpPermutationsInc(Class[][] sortedSignatures, Object[] args, boolean varargs) {
-        final ArgumentTypes argTs = new ArgumentTypes(args);
-        for (int i = 0; i < sortedSignatures.length; i++) {
-            for (int j = 0; j < sortedSignatures.length; j++) {
-                assertEquals("sortedSignatures[" + i + "] <==> sortedSignatures [" + j + "]",
-                        _NumberUtil.getSignum(
-                                Integer.valueOf(j).compareTo(i)),
-                        _NumberUtil.getSignum(
-                                argTs.compareParameterListPreferability(
-                                        sortedSignatures[i], sortedSignatures[j], varargs)));
-            }
-        }
-    }
-
-    private void testAllCmpPermutationsEqu(Class[][] signatures, Object[] args) {
-        final ArgumentTypes argTs = new ArgumentTypes(args);
-        for (int i = 0; i < signatures.length; i++) {
-            for (int j = 0; j < signatures.length; j++) {
-                assertEquals("sortedSignatures[" + i + "] <==> sortedSignatures [" + j + "]",
-                        0,
-                        argTs.compareParameterListPreferability(signatures[i], signatures[j], false));
-            }
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/PrallelObjectIntrospectionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/PrallelObjectIntrospectionTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/PrallelObjectIntrospectionTest.java
deleted file mode 100644
index de84173..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/PrallelObjectIntrospectionTest.java
+++ /dev/null
@@ -1,43 +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.beans;
-
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-public class PrallelObjectIntrospectionTest extends AbstractParallelIntrospectionTest {
-
-    public PrallelObjectIntrospectionTest(String name) {
-        super(name);
-    }
-
-    public static void main(String[] args) {
-        new PrallelObjectIntrospectionTest("almostForever")
-                .testReliability(Integer.MAX_VALUE);
-    }
-    
-    @Override
-    protected TemplateHashModel getWrappedEntity(int objIdx)
-    throws TemplateModelException {
-        return (TemplateHashModel) getBeansWrapper().wrap(
-                ManyObjectsOfDifferentClasses.OBJECTS[objIdx]);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/PrallelStaticIntrospectionTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/PrallelStaticIntrospectionTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/PrallelStaticIntrospectionTest.java
deleted file mode 100644
index a47d0e4..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/PrallelStaticIntrospectionTest.java
+++ /dev/null
@@ -1,47 +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.beans;
-
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-public class PrallelStaticIntrospectionTest extends AbstractParallelIntrospectionTest {
-
-    private static final String STATICS_CLASS_CONTAINER_CLASS_NAME
-            = ManyStaticsOfDifferentClasses.class.getName();
-
-    public PrallelStaticIntrospectionTest(String name) {
-        super(name);
-    }
-
-    public static void main(String[] args) {
-        new PrallelStaticIntrospectionTest("almostForever")
-                .testReliability(Integer.MAX_VALUE);
-    }
-    
-    @Override
-    protected TemplateHashModel getWrappedEntity(int clIdx)
-    throws TemplateModelException {
-        return (TemplateHashModel) getBeansWrapper().getStaticModels().get(
-                STATICS_CLASS_CONTAINER_CLASS_NAME + "$C"
-                + clIdx);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/RationalNumber.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/RationalNumber.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/RationalNumber.java
deleted file mode 100644
index 8e05262..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/RationalNumber.java
+++ /dev/null
@@ -1,90 +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.beans;
-
-public final class RationalNumber extends Number {
-    
-    final int divident;
-    final int divisor;
-    
-    public RationalNumber(int divident, int divisor) {
-        this.divident = divident;
-        this.divisor = divisor;
-    }
-
-    @Override
-    public int intValue() {
-        return divident / divisor;
-    }
-
-    @Override
-    public long longValue() {
-        return divident / (long) divisor;
-    }
-
-    @Override
-    public float floatValue() {
-        return (float) (divident / (double) divisor);
-    }
-
-    @Override
-    public double doubleValue() {
-        return divident / (double) divisor;
-    }
-
-    public int getDivident() {
-        return divident;
-    }
-
-    public int getDivisor() {
-        return divisor;
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + divident;
-        result = prime * result + divisor;
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        RationalNumber other = (RationalNumber) obj;
-        if (divident != other.divident)
-            return false;
-        if (divisor != other.divisor)
-            return false;
-        return true;
-    }
-
-    @Override
-    public String toString() {
-        return divident + "/" + divisor;
-    }
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/StaticModelsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/StaticModelsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/StaticModelsTest.java
deleted file mode 100644
index 35e9e7a..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/StaticModelsTest.java
+++ /dev/null
@@ -1,90 +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.beans;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.util.ArrayList;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class StaticModelsTest {
-
-    @Test
-    public void modelCaching() throws Exception {
-        BeansWrapper bw = new BeansWrapper(Configuration.VERSION_3_0_0);
-        TemplateHashModel statics = bw.getStaticModels();
-        TemplateHashModel s = (TemplateHashModel) statics.get(S.class.getName());
-        assertNotNull(s);
-        assertNotNull(s.get("F"));
-        assertNotNull(s.get("m"));
-        try {
-            s.get("x");
-            fail();
-        } catch (TemplateModelException e) {
-            assertThat(e.getMessage(), containsString("No such key"));
-        }
-        
-        try {
-            statics.get("no.such.ClassExists");
-            fail();
-        } catch (TemplateModelException e) {
-            assertTrue(e.getCause() instanceof ClassNotFoundException);
-        }
-        
-        TemplateModel f = s.get("F");
-        assertTrue(f instanceof TemplateScalarModel);
-        assertEquals(((TemplateScalarModel) f).getAsString(), "F OK");
-        
-        TemplateModel m = s.get("m");
-        assertTrue(m instanceof TemplateMethodModelEx);
-        assertEquals(((TemplateScalarModel) ((TemplateMethodModelEx) m).exec(new ArrayList())).getAsString(), "m OK");
-        
-        assertSame(s, statics.get(S.class.getName()));
-        
-        bw.clearClassIntrospecitonCache();
-        TemplateHashModel sAfterClean = (TemplateHashModel) statics.get(S.class.getName());
-        assertNotSame(s, sAfterClean);
-        assertSame(sAfterClean, statics.get(S.class.getName()));
-        assertNotNull(sAfterClean.get("F"));
-        assertNotNull(sAfterClean.get("m"));
-    }
-    
-    public static class S {
-        
-        public static final String F = "F OK"; 
-        
-        public static String m() {
-            return "m OK";
-        }
-        
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/beans/TypeFlagsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/beans/TypeFlagsTest.java b/src/test/java/org/apache/freemarker/core/model/impl/beans/TypeFlagsTest.java
deleted file mode 100644
index 301e08f..0000000
--- a/src/test/java/org/apache/freemarker/core/model/impl/beans/TypeFlagsTest.java
+++ /dev/null
@@ -1,671 +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.beans;
-
-import java.io.File;
-import java.lang.reflect.Method;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.freemarker.core.Configuration;
-
-import junit.framework.TestCase;
-
-public class TypeFlagsTest extends TestCase {
-
-    public TypeFlagsTest(String name) {
-        super(name);
-    }
-    
-    private final BeansWrapper bw = new BeansWrapper(Configuration.VERSION_3_0_0); 
-
-    public void testSingleNumType() {
-        checkTypeFlags(SingleNumTypeC.class, "mInt",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.INTEGER | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                | TypeFlags.ACCEPTS_STRING);
-        checkTypeFlags(SingleNumTypeC.class, "mLong",
-                TypeFlags.LONG | TypeFlags.ACCEPTS_NUMBER);
-        checkTypeFlags(SingleNumTypeC.class, "mShort",
-                TypeFlags.SHORT | TypeFlags.ACCEPTS_NUMBER);
-        checkTypeFlags(SingleNumTypeC.class, "mByte",
-                TypeFlags.BYTE | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.ACCEPTS_ANY_OBJECT);
-        checkTypeFlags(SingleNumTypeC.class, "mDouble",
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER);
-        checkTypeFlags(SingleNumTypeC.class, "mFloat",
-                TypeFlags.FLOAT | TypeFlags.ACCEPTS_NUMBER);
-        checkTypeFlags(SingleNumTypeC.class, "mUnknown",
-                TypeFlags.UNKNOWN_NUMERICAL_TYPE | TypeFlags.ACCEPTS_NUMBER);
-        
-        checkTypeFlags(SingleNumTypeC.class, "mVarParamCnt",
-                TypeFlags.BIG_DECIMAL | TypeFlags.ACCEPTS_NUMBER);
-        checkTypeFlags(SingleNumTypeC.class, "mVarParamCnt",
-                TypeFlags.BIG_INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER);
-        checkTypeFlags(SingleNumTypeC.class, "mVarParamCnt",
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.FLOAT | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER);
-    }
-    
-    static public class SingleNumTypeC {
-        public void mInt(int a1, String a2) { }
-        public void mInt(int a1, int a2) { }
-        public void mLong(long a1) { }
-        public void mLong(Long a1) { }
-        public void mShort(short a1) { }
-        public void mByte(byte a1, boolean a2) { }
-        public void mByte(byte a1, String a2) { }
-        public void mByte(byte a1, Object a2) { }
-        public void mDouble(double a1) { }
-        public void mFloat(float a1) { }
-        public void mUnknown(RationalNumber a1) { };
-        
-        public void mVarParamCnt(BigDecimal a1) { }
-        public void mVarParamCnt(BigInteger a1, Double a2) { }
-        public void mVarParamCnt(Double a1,     Float a2, Integer a3) { }
-        public void mVarParamCnt(Object a1,     char a2,  boolean a3, File a4, Map a5,    Boolean a6) { }
-        public void mVarParamCnt(Long a1,       int a2,   short a3,   byte a4, double a5, float a6) { }
-    }
-
-    public void testMultipleNumTypes() {
-        checkTypeFlags(MultiNumTypeC.class, "m1",
-                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT 
-                | TypeFlags.BYTE | TypeFlags.DOUBLE | TypeFlags.INTEGER
-                | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(MultiNumTypeC.class, "m2",
-                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT 
-                | TypeFlags.SHORT | TypeFlags.LONG | TypeFlags.FLOAT
-                | TypeFlags.ACCEPTS_NUMBER | TypeFlags.CHARACTER
-                );
-
-        checkTypeFlags(MultiNumTypeC.class, "m3",
-                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT 
-                | TypeFlags.BIG_DECIMAL | TypeFlags.BIG_INTEGER
-                | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.BIG_INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT
-                | TypeFlags.BIG_DECIMAL | TypeFlags.UNKNOWN_NUMERICAL_TYPE
-                | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(MultiNumTypeC.class, "m4",
-                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.FLOAT | TypeFlags.ACCEPTS_NUMBER
-                | TypeFlags.CHARACTER
-                );
-        
-        checkTypeFlags(MultiNumTypeC.class, "m5",
-                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT
-                | TypeFlags.FLOAT | TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(MultiNumTypeC.class, "m6",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER
-                );
-        assertSame(getTypeFlags(MultiNumTypeC.class, "m6", false, 2), OverloadedMethodsSubset.ALL_ZEROS_ARRAY);
-        assertSame(getTypeFlags(MultiNumTypeC.class, "m6", true, 2), OverloadedMethodsSubset.ALL_ZEROS_ARRAY);
-        assertSame(getTypeFlags(MultiNumTypeC.class, "m6", false, 3), OverloadedMethodsSubset.ALL_ZEROS_ARRAY);
-        assertSame(getTypeFlags(MultiNumTypeC.class, "m6", true, 3), OverloadedMethodsSubset.ALL_ZEROS_ARRAY);
-        checkTypeFlags(MultiNumTypeC.class, "m6",
-                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
-                0
-                );
-    }
-    
-    static public class MultiNumTypeC {
-        public void m1(byte a1) { };
-        public void m1(int a1) { };
-        public void m1(double a2) { };
-        
-        public void m2(short a1) { };
-        public void m2(long a1) { };
-        public void m2(float a1) { };
-        public void m2(char a1) { };
-        
-        public void m3(BigInteger a1, BigInteger a2, BigDecimal a3) { };
-        public void m3(BigDecimal a1, BigInteger a2, RationalNumber a3) { };
-        
-        public void m4(float a1) { };
-        public void m4(char a1) { };
-        
-        public void m5(Float a1) { };
-        public void m5(Double a1) { };
-        public void m5(Enum a1) { };
-        
-        public void m6(int a1) { };
-        public void m6(File a1, Throwable a2) { };
-        public void m6(File a1, Throwable a2, StringBuilder a3) { };
-        public void m6(File a1, Throwable a2, Throwable a3) { };
-        public void m6(double a1, int a2, File a3, File a4) { };
-        public void m6(File a1, int a2, double a3, File a4) { };
-    }
-
-    public void testVarargsNums() {
-        checkTypeFlags(VarArgsC.class, "m1",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER
-                );
-        checkTypeFlags(VarArgsC.class, "m2",
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m3",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER
-                );
-        checkTypeFlags(VarArgsC.class, "m3",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.INTEGER
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-        checkTypeFlags(VarArgsC.class, "m3",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.INTEGER
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.INTEGER
-                | TypeFlags.BIG_DECIMAL | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m4",
-                TypeFlags.INTEGER | TypeFlags.LONG
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m5",
-                TypeFlags.LONG | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m6",
-                TypeFlags.LONG | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                | TypeFlags.ACCEPTS_STRING
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m7",
-                TypeFlags.INTEGER | TypeFlags.BYTE
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.FLOAT
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-
-        checkTypeFlags(VarArgsC.class, "m8",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.FLOAT
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m9",
-                TypeFlags.INTEGER | TypeFlags.BYTE
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m10",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER
-                );
-        checkTypeFlags(VarArgsC.class, "m10",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.LONG | TypeFlags.DOUBLE
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m11",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.SHORT | TypeFlags.ACCEPTS_NUMBER
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-        checkTypeFlags(VarArgsC.class, "m11",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.SHORT
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.LONG | TypeFlags.DOUBLE
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m12",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER
-                );
-        checkTypeFlags(VarArgsC.class, "m12",
-                TypeFlags.INTEGER | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.SHORT
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.BYTE
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.LONG | TypeFlags.DOUBLE
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-        
-        checkTypeFlags(VarArgsC.class, "m13",
-                TypeFlags.CHARACTER,
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER);
-        checkTypeFlags(VarArgsC.class, "m13",
-                TypeFlags.CHARACTER,
-                TypeFlags.DOUBLE | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.UNKNOWN_NUMERICAL_TYPE
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER,
-                TypeFlags.DOUBLE | TypeFlags.LONG
-                | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT | TypeFlags.ACCEPTS_NUMBER
-                );
-    }
-    
-    static public class VarArgsC {
-        public void m1(int... va) { }
-        
-        public void m2(double a1, int... va) { }
-        
-        public void m3(int... va) { }
-        public void m3(int a1, double... va) { }
-        public void m3(int a1, double a2, BigDecimal... va) { }
-        
-        public void m4(int... va) { }
-        public void m4(long... va) { }
-
-        public void m5(Long... va) { }
-        public void m5(long... va) { }
-        
-        public void m6(long... va) { }
-        public void m6(String... va) { }
-        
-        public void m7(int a1, double... va) { }
-        public void m7(byte a1, float... va) { }
-
-        public void m8(int a1, double... va) { }
-        public void m8(int a1, float... va) { }
-        
-        public void m9(int a1, double... va) { }
-        public void m9(byte a1, double... va) { }
-        
-        public void m10(int a1, double a2, long... va) { }
-        public void m10(int a1, double... va) { }
-        
-        public void m11(int a1, short a2, long... va) { }
-        public void m11(int a1, double... va) { }
-        
-        public void m12(int a1, short a2, byte a3, long... va) { }
-        public void m12(int a1, double... va) { }
-        
-        public void m13(char a1, double a2, RationalNumber a3, Long... va) { }
-        public void m13(char a1, Double... va) { }
-    }
-    
-    public void testAllZeros() {
-        for (boolean reverse : new boolean[] { true, false }) {
-            assertSame(OverloadedMethodsSubset.ALL_ZEROS_ARRAY, getTypeFlags(AllZeroC.class, "m1", reverse, 0));
-            assertSame(OverloadedMethodsSubset.ALL_ZEROS_ARRAY, getTypeFlags(AllZeroC.class, "m2", reverse, 2));
-            assertSame(OverloadedMethodsSubset.ALL_ZEROS_ARRAY, getTypeFlags(AllZeroC.class, "m3", reverse, 1));
-        }
-    }
-    
-    static public class AllZeroC {
-        public void m1() {}
-        
-        public void m2(File a1, File a2) {}
-        
-        public void m3(File a1) {}
-        public void m3(StringBuilder a1) {}
-    }
-
-    public void testAcceptanceNonOverloaded() {
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber1",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.UNKNOWN_NUMERICAL_TYPE);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber2",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.BYTE);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber3",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.BYTE);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber4",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.SHORT);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber5",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.SHORT);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber6",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.INTEGER);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber7",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.INTEGER);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber8",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.LONG);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber9",  TypeFlags.ACCEPTS_NUMBER | TypeFlags.LONG);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber10", TypeFlags.ACCEPTS_NUMBER | TypeFlags.FLOAT);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber11", TypeFlags.ACCEPTS_NUMBER | TypeFlags.FLOAT);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber12", TypeFlags.ACCEPTS_NUMBER | TypeFlags.DOUBLE);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber13", TypeFlags.ACCEPTS_NUMBER | TypeFlags.DOUBLE);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber14", TypeFlags.ACCEPTS_NUMBER | TypeFlags.BIG_INTEGER);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber15", TypeFlags.ACCEPTS_NUMBER | TypeFlags.BIG_DECIMAL);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mNumber16", TypeFlags.ACCEPTS_NUMBER | TypeFlags.UNKNOWN_NUMERICAL_TYPE);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mDate", TypeFlags.ACCEPTS_DATE);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mSQLDate1", 0);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mSQLDate2", 0);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mString", TypeFlags.ACCEPTS_STRING);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mCharSequence", TypeFlags.ACCEPTS_STRING);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mStringBuilder", 0);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mBool", TypeFlags.ACCEPTS_BOOLEAN);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mBoolean", TypeFlags.ACCEPTS_BOOLEAN);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mMap1", TypeFlags.ACCEPTS_MAP);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mMap2", 0);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mList1", TypeFlags.ACCEPTS_LIST);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mList2", 0);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mSet1", TypeFlags.ACCEPTS_SET);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mSet2", 0);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mCollection", TypeFlags.ACCEPTS_SET | TypeFlags.ACCEPTS_LIST);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mChar1", TypeFlags.CHARACTER);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mChar2", TypeFlags.CHARACTER);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mArray1", TypeFlags.ACCEPTS_ARRAY);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mArray2", TypeFlags.ACCEPTS_ARRAY);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mArray3", TypeFlags.ACCEPTS_ARRAY);
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mArray4", TypeFlags.ACCEPTS_ARRAY);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mObject", TypeFlags.ACCEPTS_ANY_OBJECT);
-        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_NUMBER) != 0);
-        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_STRING) != 0);
-        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_BOOLEAN) != 0);
-        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_MAP) != 0);
-        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_LIST) != 0);
-        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_SET) != 0);
-        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.ACCEPTS_ARRAY) != 0);
-        assertTrue((TypeFlags.ACCEPTS_ANY_OBJECT & TypeFlags.CHARACTER) == 0);  // deliberatly 0 
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mMapDate", 0);
-        
-        checkTypeFlags(AcceptanceNonoverloadedC.class, "mDateBooleanList",
-                TypeFlags.ACCEPTS_DATE, TypeFlags.ACCEPTS_BOOLEAN, TypeFlags.ACCEPTS_LIST);
-    }
-    
-    static public class AcceptanceNonoverloadedC {
-        public void mNumber1(Number a1) {}
-        public void mNumber2(Byte a1) {}
-        public void mNumber3(byte a1) {}
-        public void mNumber4(Short a1) {}
-        public void mNumber5(short a1) {}
-        public void mNumber6(Integer a1) {}
-        public void mNumber7(int a1) {}
-        public void mNumber8(Long a1) {}
-        public void mNumber9(long a1) {}
-        public void mNumber10(Float a1) {}
-        public void mNumber11(float a1) {}
-        public void mNumber12(Double a1) {}
-        public void mNumber13(double a1) {}
-        public void mNumber14(BigInteger a1) {}
-        public void mNumber15(BigDecimal a1) {}
-        public void mNumber16(RationalNumber a1) {}
-        
-        public void mDate(Date a1) {}
-        public void mSQLDate1(java.sql.Date a1) {}
-        public void mSQLDate2(java.sql.Timestamp a1) {}
-        
-        public void mString(String a1) {}
-        public void mCharSequence(CharSequence a1) {}
-        public void mStringBuilder(StringBuilder a1) {}
-        
-        public void mBool(boolean a1) {}
-        public void mBoolean(Boolean a1) {}
-
-        public void mMap1(Map a1) {}
-        public void mMap2(LinkedHashMap a1) {}
-        
-        public void mList1(List a1) {}
-        public void mList2(ArrayList a1) {}
-        
-        public void mSet1(Set a1) {}
-        public void mSet2(HashSet a1) {}
-        
-        public void mCollection(Collection a1) {}
-        
-        public void mMapDate(MapDate a1) {}
-
-        public void mChar1(Character a1) {}
-        public void mChar2(char a1) {}
-
-        public void mArray1(Object[] a1) {}
-        public void mArray2(int[] a1) {}
-        public void mArray3(Integer[] a1) {}
-        public void mArray4(Void[] a1) {}
-        
-        public void mObject(Object a1) {}
-        
-        public void mDateBooleanList(Date a1, boolean a2, List a3) {}
-    }
-
-    public void testAcceptanceOverloaded() {
-        checkTypeFlags(AcceptanceOverloadedC.class, "mLongDateList",
-                TypeFlags.ACCEPTS_NUMBER | TypeFlags.LONG | TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT
-                | TypeFlags.ACCEPTS_DATE | TypeFlags.ACCEPTS_LIST);
-        checkTypeFlags(AcceptanceOverloadedC.class, "mBoolean", TypeFlags.ACCEPTS_BOOLEAN);
-        checkTypeFlags(AcceptanceOverloadedC.class, "mStringChar",
-                TypeFlags.ACCEPTS_STRING | TypeFlags.CHARACTER);
-        checkTypeFlags(AcceptanceOverloadedC.class, "mStringFile", TypeFlags.ACCEPTS_STRING);
-        checkTypeFlags(AcceptanceOverloadedC.class, "mMapObject", TypeFlags.ACCEPTS_ANY_OBJECT);
-        checkTypeFlags(AcceptanceOverloadedC.class, "mSetMap", TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_SET);
-        checkTypeFlags(AcceptanceOverloadedC.class, "mCollectionMap",
-                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_SET | TypeFlags.ACCEPTS_LIST);
-        checkTypeFlags(AcceptanceOverloadedC.class, "mArray", TypeFlags.ACCEPTS_ARRAY);
-        checkTypeFlags(AcceptanceOverloadedC.class, "mArrayList", TypeFlags.ACCEPTS_ARRAY | TypeFlags.ACCEPTS_LIST);
-        
-        checkTypeFlags(AcceptanceOverloadedC.class, "mStringCollectionThenBooleanThenMapList",
-                TypeFlags.ACCEPTS_STRING | TypeFlags.ACCEPTS_LIST | TypeFlags.ACCEPTS_SET,
-                TypeFlags.ACCEPTS_BOOLEAN,
-                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_LIST);
-    }
-    
-    static public class AcceptanceOverloadedC {
-        public void mLongDateList(long a1) {}
-        public void mLongDateList(Date a1) {}
-        public void mLongDateList(List a1) {}
-
-        public void mBoolean(boolean a1) {}
-        public void mBoolean(Boolean a1) {}
-        
-        public void mDate(Date a1) {}
-        public void mDate(java.sql.Date a1) {}
-        public void mDate(java.sql.Timestamp a1) {}
-        
-        public void mStringChar(String a1) {}
-        public void mStringChar(char a1) {}
-        public void mStringChar(Character a1) {}
-
-        public void mStringFile(String a1) {}
-        public void mStringFile(File a1) {}
-        
-        public void mMapObject(Map a1) {}
-        public void mMapObject(Object a1) {}
-        
-        public void mSetMap(Set a1) {}
-        public void mSetMap(Map a1) {}
-        
-        public void mCollectionMap(Collection a1) {}
-        public void mCollectionMap(Map a1) {}
-        
-        public void mArray(Object[] a1) {}
-        public void mArray(int[] a1) {}
-        public void mArray(Integer[] a1) {}
-        public void mArray(Void[] a1) {}
-        
-        public void mArrayList(String[] a1) {}
-        public void mArrayList(List a1) {}
-        
-        public void mStringCollectionThenBooleanThenMapList(String a1, boolean a2, Map a3) {}
-        public void mStringCollectionThenBooleanThenMapList(Collection a1, boolean a2, Map a3) {}
-        public void mStringCollectionThenBooleanThenMapList(String a1, boolean a2, List a3) {}
-    }
-
-    public void testAcceptanceVarargsC() {
-        checkTypeFlags(AcceptanceVarArgsC.class, "m1",
-                TypeFlags.ACCEPTS_LIST | TypeFlags.ACCEPTS_STRING);
-        
-        checkTypeFlags(AcceptanceVarArgsC.class, "m2",
-                TypeFlags.ACCEPTS_MAP,
-                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_STRING | TypeFlags.ACCEPTS_BOOLEAN,
-                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_STRING);
-        checkTypeFlags(AcceptanceVarArgsC.class, "m2",
-                TypeFlags.ACCEPTS_MAP,
-                TypeFlags.ACCEPTS_MAP | TypeFlags.ACCEPTS_STRING | TypeFlags.ACCEPTS_BOOLEAN);
-        checkTypeFlags(AcceptanceVarArgsC.class, "m2",
-                TypeFlags.ACCEPTS_MAP);
-        
-        checkTypeFlags(AcceptanceVarArgsC.class, "m3",
-                TypeFlags.ACCEPTS_BOOLEAN);
-        checkTypeFlags(AcceptanceVarArgsC.class, "m3",
-                TypeFlags.ACCEPTS_BOOLEAN | TypeFlags.ACCEPTS_STRING,
-                TypeFlags.ACCEPTS_BOOLEAN | TypeFlags.ACCEPTS_MAP,
-                TypeFlags.ACCEPTS_BOOLEAN | TypeFlags.CHARACTER,
-                TypeFlags.ACCEPTS_BOOLEAN);
-    }
-    
-    static public class AcceptanceVarArgsC {
-        public void m1(List... a1) {}
-        public void m1(String... a1) {}
-
-        public void m2(Map a1, String... a2) {}
-        public void m2(Map a1, boolean a2, String... a3) {}
-        public void m2(Map... a1) {}
-        
-        public void m3(String a1, Map a2, char a3, Boolean... a4) {}
-        public void m3(boolean... a1) {}
-    }
-
-    static public class MapDate extends Date implements Map {
-    
-        @Override
-        public int size() {
-            return 0;
-        }
-    
-        @Override
-        public boolean isEmpty() {
-            return false;
-        }
-    
-        @Override
-        public boolean containsKey(Object key) {
-            return false;
-        }
-    
-        @Override
-        public boolean containsValue(Object value) {
-            return false;
-        }
-    
-        @Override
-        public Object get(Object key) {
-            return null;
-        }
-    
-        @Override
-        public Object put(Object key, Object value) {
-            return null;
-        }
-    
-        @Override
-        public Object remove(Object key) {
-            return null;
-        }
-    
-        @Override
-        public void putAll(Map m) {
-        }
-    
-        @Override
-        public void clear() {
-        }
-    
-        @Override
-        public Set keySet() {
-            return null;
-        }
-    
-        @Override
-        public Collection values() {
-            return null;
-        }
-    
-        @Override
-        public Set entrySet() {
-            return null;
-        }
-        
-    }
-
-    private OverloadedMethodsSubset newOverloadedMethodsSubset(Class cl, String methodName, final boolean desc) {
-        final Method[] ms = cl.getMethods();
-        
-        final List<Method> filteredMethods = new ArrayList();
-        for (Method m : ms) {
-            if (m.getName().equals(methodName)) {
-                filteredMethods.add(m);
-            }
-        }
-        // As the order in which getMethods() returns the methods is undefined, we sort them for test predictability: 
-        Collections.sort(filteredMethods, new Comparator<Method>() {
-            @Override
-            public int compare(Method o1, Method o2) {
-                int res = o1.toString().compareTo(o2.toString());
-                return desc ? -res : res;
-            }
-        });
-        
-        final OverloadedMethodsSubset oms = cl.getName().indexOf("VarArgs") == -1
-                ? new OverloadedFixArgsMethods() : new OverloadedVarArgsMethods();
-        for (Method m : filteredMethods) {
-            oms.addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(m, m.getParameterTypes()));
-        }
-        return oms;
-    }
-    
-    private void checkTypeFlags(Class cl, String methodName, int... expectedTypeFlags) {
-        checkTypeFlags(cl, methodName, false, expectedTypeFlags);
-        checkTypeFlags(cl, methodName, true, expectedTypeFlags);
-    }
-    
-    private void checkTypeFlags(Class cl, String methodName, boolean revMetOrder, int... expectedTypeFlags) {
-        int[] actualParamTypes = getTypeFlags(cl, methodName, revMetOrder, expectedTypeFlags.length);
-        assertNotNull("Method " + methodName + "(#" + expectedTypeFlags.length + ") doesn't exist", actualParamTypes);
-        if (actualParamTypes != OverloadedMethodsSubset.ALL_ZEROS_ARRAY) {
-            assertEquals(expectedTypeFlags.length, actualParamTypes.length);
-            for (int i = 0; i < expectedTypeFlags.length; i++) {
-                assertEquals(expectedTypeFlags[i], actualParamTypes[i]);
-            }
-        } else {
-            for (int expectedTypeFlag : expectedTypeFlags) {
-                assertEquals(expectedTypeFlag, 0);
-            }
-        }
-    }
-
-    private int[] getTypeFlags(Class cl, String methodName, boolean revMetOrder, int paramCnt) {
-        OverloadedMethodsSubset oms = newOverloadedMethodsSubset(cl, methodName, revMetOrder);
-        int[] actualParamTypes = oms.getTypeFlags(paramCnt);
-        return actualParamTypes;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 d190704..26205d9 100644
--- a/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/jsp/RealServletContainertTest.java
@@ -41,9 +41,8 @@ import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateExceptionHandler;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder;
 import org.apache.freemarker.core.model.impl.SimpleObjectWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader;
 import org.apache.freemarker.servlet.FreemarkerServlet;
@@ -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);
-            BeansWrapperBuilder bwb = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapperBuilder bwb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
             bwb.setUseModelCache(true);
             cfg.setObjectWrapper(bwb.build());
             cfg.setTemplateLoader(new WebAppTemplateLoader(getServletContext()));
@@ -428,9 +427,9 @@ public class RealServletContainertTest extends WebAppTestCase {
             
             {
                 ObjectWrapper ow = cfg.getObjectWrapper();
-                assertSame(BeansWrapper.class, ow.getClass());
-                assertTrue(((BeansWrapper) ow).getUseModelCache());
-                assertEquals(Configuration.VERSION_3_0_0, ((BeansWrapper) ow).getIncompatibleImprovements());
+                assertSame(DefaultObjectWrapper.class, ow.getClass());
+                assertTrue(((DefaultObjectWrapper) ow).getUseModelCache());
+                assertEquals(Configuration.VERSION_3_0_0, ((DefaultObjectWrapper) ow).getIncompatibleImprovements());
             }
             
             {
@@ -447,13 +446,13 @@ public class RealServletContainertTest extends WebAppTestCase {
         @Override
         protected void doAssertions(Configuration cfg) throws Exception {
             ObjectWrapper ow = cfg.getObjectWrapper();
-            assertSame(BeansWrapper.class, ow.getClass());
-            assertTrue(((BeansWrapper) ow).getUseModelCache());
+            assertSame(DefaultObjectWrapper.class, ow.getClass());
+            assertTrue(((DefaultObjectWrapper) ow).getUseModelCache());
         }
 
         @Override
         protected ObjectWrapper createDefaultObjectWrapper() {
-            BeansWrapperBuilder bwb = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapperBuilder bwb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
             bwb.setUseModelCache(true);
             assertEquals(Configuration.VERSION_3_0_0, bwb.getIncompatibleImprovements());
             return bwb.build();
@@ -475,7 +474,7 @@ public class RealServletContainertTest extends WebAppTestCase {
         protected void doAssertions(Configuration cfg) throws Exception {
             ObjectWrapper ow = cfg.getObjectWrapper();
             assertSame(SimpleObjectWrapper.class, ow.getClass());
-            assertEquals(Configuration.VERSION_3_0_0, ((BeansWrapper) ow).getIncompatibleImprovements());
+            assertEquals(Configuration.VERSION_3_0_0, ((DefaultObjectWrapper) ow).getIncompatibleImprovements());
         }
         
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 396e8aa..e2468df 100644
--- a/src/test/java/org/apache/freemarker/servlet/jsp/TLDParsingTest.java
+++ b/src/test/java/org/apache/freemarker/servlet/jsp/TLDParsingTest.java
@@ -35,9 +35,9 @@ import javax.xml.parsers.SAXParserFactory;
 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.apache.freemarker.core.model.impl.beans.BeansWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,11 +48,11 @@ import org.xml.sax.XMLReader;
 @RunWith(JUnit4.class)
 public class TLDParsingTest {
 
-    private BeansWrapper wrapper;
+    private DefaultObjectWrapper wrapper;
 
     @Before
     public void before() throws Exception {
-        wrapper = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        wrapper = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 4fab03b..cce84a4 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/TemplateTestCase.java
@@ -54,17 +54,15 @@ import org.apache.freemarker.core.model.TemplateMethodModel;
 import org.apache.freemarker.core.model.TemplateNodeModel;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.BooleanModel;
 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;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.model.impl._StaticObjectWrappers;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
-import org.apache.freemarker.core.model.impl.beans.BooleanModel;
-import org.apache.freemarker.core.model.impl.beans.Java7MembersOnlyBeansWrapper;
-import org.apache.freemarker.core.model.impl.beans.ResourceBundleModel;
 import org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
 import org.apache.freemarker.core.util._NullArgumentException;
 import org.apache.freemarker.core.util._NullWriter;
@@ -183,7 +181,7 @@ public class TemplateTestCase extends FileTestCase {
         conf.setTemplateLoader(new CopyrightCommentRemoverTemplateLoader(
                 new FileTemplateLoader(new File(getTestClassDirectory(), "templates"))));
         
-        BeansWrapper beansWrapper = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        DefaultObjectWrapper dow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
         
         dataModel.put(ASSERT_VAR_NAME, AssertDirective.INSTANCE);
         dataModel.put(ASSERT_EQUALS_VAR_NAME, AssertEqualsDirective.INSTANCE);
@@ -201,46 +199,7 @@ public class TemplateTestCase extends FileTestCase {
             dataModel.put("list", ImmutableList.of(1, 2, 3));
             dataModel.put("set", ImmutableSet.of("a", "b", "c"));
             dataModel.put("s", "test");
-        } else if (simpleTestName.equals("bean-maps")) {
-            BeansWrapper w1 = new Java7MembersOnlyBeansWrapper();
-            BeansWrapper w2 = new Java7MembersOnlyBeansWrapper();
-            BeansWrapper w3 = new Java7MembersOnlyBeansWrapper();
-            BeansWrapper w4 = new Java7MembersOnlyBeansWrapper();
-            BeansWrapper w5 = new Java7MembersOnlyBeansWrapper();
-            BeansWrapper w6 = new Java7MembersOnlyBeansWrapper();
-            BeansWrapper w7 = new Java7MembersOnlyBeansWrapper();
-    
-            w1.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
-            w2.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
-            w3.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
-            w4.setExposureLevel(BeansWrapper.EXPOSE_NOTHING);
-            w5.setExposureLevel(BeansWrapper.EXPOSE_ALL);
-            w6.setExposureLevel(BeansWrapper.EXPOSE_ALL);
-    
-            w1.setMethodsShadowItems(true);
-            w2.setMethodsShadowItems(false);
-            w3.setMethodsShadowItems(true);
-            w4.setMethodsShadowItems(false);
-            w5.setMethodsShadowItems(true);
-            w6.setMethodsShadowItems(false);
-    
-            w7.setSimpleMapWrapper(true);
-    
-            Object test = getTestMapBean();
-    
-            dataModel.put("m1", w1.wrap(test));
-            dataModel.put("m2", w2.wrap(test));
-            dataModel.put("m3", w3.wrap(test));
-            dataModel.put("m4", w4.wrap(test));
-            dataModel.put("m5", w5.wrap(test));
-            dataModel.put("m6", w6.wrap(test));
-            dataModel.put("m7", w7.wrap(test));
-    
-            dataModel.put("s1", w1.wrap("hello"));
-            dataModel.put("s2", w1.wrap("world"));
-            dataModel.put("s3", w5.wrap("hello"));
-            dataModel.put("s4", w5.wrap("world"));
-        } else if (simpleTestName.equals("beans")) {
+        } else if (simpleTestName.equals("default-object-wrapper")) {
             dataModel.put("array", new String[] { "array-0", "array-1"});
             dataModel.put("list", Arrays.asList("list-0", "list-1", "list-2"));
             Map<Object, Object> tmap = new HashMap<>();
@@ -253,10 +212,10 @@ public class TemplateTestCase extends FileTestCase {
             dataModel.put("resourceBundle",
                     new ResourceBundleModel(ResourceBundle.getBundle(
                             "org.apache.freemarker.test.templatesuite.models.BeansTestResources"),
-                            _StaticObjectWrappers.BEANS_WRAPPER));
+                            _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER));
             dataModel.put("date", new GregorianCalendar(1974, 10, 14).getTime());
-            dataModel.put("statics", beansWrapper.getStaticModels());
-            dataModel.put("enums", beansWrapper.getEnumModels());
+            dataModel.put("statics", dow.getStaticModels());
+            dataModel.put("enums", dow.getEnumModels());
         } else if (simpleTestName.equals("boolean")) {
             dataModel.put( "boolean1", TemplateBooleanModel.FALSE);
             dataModel.put( "boolean2", TemplateBooleanModel.TRUE);
@@ -381,7 +340,7 @@ public class TemplateTestCase extends FileTestCase {
             abcSet.add("b");
             abcSet.add("c");
             dataModel.put("abcSet", abcSet);
-            dataModel.put("abcSetNonSeq", DefaultNonListCollectionAdapter.adapt(abcSet, beansWrapper));
+            dataModel.put("abcSetNonSeq", DefaultNonListCollectionAdapter.adapt(abcSet, dow));
             
             List<String> listWithNull = new ArrayList<>();
             listWithNull.add("a");
@@ -408,7 +367,7 @@ public class TemplateTestCase extends FileTestCase {
         } else if (simpleTestName.equals("varargs")) {
           dataModel.put("m", new VarArgTestModel());
         } else if (simpleTestName.startsWith("boolean-formatting")) {
-          dataModel.put("beansBoolean", new BooleanModel(Boolean.TRUE, (BeansWrapper) conf.getObjectWrapper()));
+          dataModel.put("beansBoolean", new BooleanModel(Boolean.TRUE, (DefaultObjectWrapper) conf.getObjectWrapper()));
           dataModel.put("booleanAndString", new BooleanAndStringTemplateModel());
           dataModel.put("booleanVsStringMethods", new BooleanVsStringMethods());
         } else if (simpleTestName.startsWith("number-math-builtins")) {
@@ -436,8 +395,6 @@ public class TemplateTestCase extends FileTestCase {
             dataModel.put("bdp", BigDecimal.valueOf(0.05));
         } else if (simpleTestName.startsWith("overloaded-methods")) {
             dataModel.put("obj", new OverloadedMethods2());
-            final boolean dow = conf.getObjectWrapper() instanceof DefaultObjectWrapper;
-            dataModel.put("dow", dow);
         }
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 da5d62f..3d98f4b 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
@@ -41,7 +41,6 @@ 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;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder;
 
 import com.google.common.collect.ImmutableMap;
 
@@ -139,10 +138,6 @@ public class Listables {
         DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
         maps.add(new SimpleHash(map, ow));
         maps.add((DefaultMapAdapter) ow.wrap(map));
-        
-        BeansWrapperBuilder bwb = new BeansWrapperBuilder(Configuration.VERSION_3_0_0);
-        bwb.setSimpleMapWrapper(true);
-        maps.add((TemplateHashModelEx2) bwb.build().wrap(map));
 
         return maps;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/test/templatesuite/models/OverloadedMethods2.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/test/templatesuite/models/OverloadedMethods2.java b/src/test/java/org/apache/freemarker/test/templatesuite/models/OverloadedMethods2.java
index c12406c..78d3462 100644
--- a/src/test/java/org/apache/freemarker/test/templatesuite/models/OverloadedMethods2.java
+++ b/src/test/java/org/apache/freemarker/test/templatesuite/models/OverloadedMethods2.java
@@ -37,7 +37,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
-import org.apache.freemarker.core.model.impl.beans.RationalNumber;
+import org.apache.freemarker.core.model.impl.RationalNumber;
 import org.apache.freemarker.core.util._StringUtil;
 import org.apache.freemarker.test.util.TestUtil;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 1c787ce..1c61994 100644
--- a/src/test/java/org/apache/freemarker/test/util/SimpleMapAndCollectionObjectWrapper.java
+++ b/src/test/java/org/apache/freemarker/test/util/SimpleMapAndCollectionObjectWrapper.java
@@ -18,6 +18,7 @@
  */
 package org.apache.freemarker.test.util;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
 
@@ -44,7 +45,7 @@ public class SimpleMapAndCollectionObjectWrapper extends DefaultObjectWrapper {
             return super.wrap(null);
         }        
         if (obj.getClass().isArray()) {
-            obj = convertArray(obj);
+            obj = Arrays.asList((Object[]) obj);
         }
         if (obj instanceof Collection) {
             return new SimpleSequence((Collection<?>) obj, this);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes-modernModels.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes-modernModels.txt b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes-modernModels.txt
index 01c8f88..c151f58 100644
--- a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes-modernModels.txt
+++ b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes-modernModels.txt
@@ -60,8 +60,8 @@ String: s [java.lang.String]
 Number: 1 [java.math.BigDecimal]
 Boolean: true [java.lang.Boolean]
 Date-time: 2014-12-20T16:19:00Z [java.util.Date]
-Sequence: [1, 2, 3] [org.apache.freemarker.core.model.impl.beans.SequenceAdapter]
-Hash: {a=1, b=2, c=3} [org.apache.freemarker.core.model.impl.beans.HashAdapter]
+Sequence: [1, 2, 3] [org.apache.freemarker.core.model.impl.SequenceAdapter]
+Hash: {a=1, b=2, c=3} [org.apache.freemarker.core.model.impl.HashAdapter]
 
 Values created in Java:
 LinkedList: [1, 2, 3] [java.util.LinkedList]

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes.txt b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes.txt
index 01c8f88..c151f58 100644
--- a/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes.txt
+++ b/src/test/resources/org/apache/freemarker/servlet/jsp/webapps/basic/WEB-INF/expected/attributes.txt
@@ -60,8 +60,8 @@ String: s [java.lang.String]
 Number: 1 [java.math.BigDecimal]
 Boolean: true [java.lang.Boolean]
 Date-time: 2014-12-20T16:19:00Z [java.util.Date]
-Sequence: [1, 2, 3] [org.apache.freemarker.core.model.impl.beans.SequenceAdapter]
-Hash: {a=1, b=2, c=3} [org.apache.freemarker.core.model.impl.beans.HashAdapter]
+Sequence: [1, 2, 3] [org.apache.freemarker.core.model.impl.SequenceAdapter]
+Hash: {a=1, b=2, c=3} [org.apache.freemarker.core.model.impl.HashAdapter]
 
 Values created in Java:
 LinkedList: [1, 2, 3] [java.util.LinkedList]

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/resources/org/apache/freemarker/test/templatesuite/expected/bean-maps.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/freemarker/test/templatesuite/expected/bean-maps.txt b/src/test/resources/org/apache/freemarker/test/templatesuite/expected/bean-maps.txt
deleted file mode 100644
index e450843..0000000
--- a/src/test/resources/org/apache/freemarker/test/templatesuite/expected/bean-maps.txt
+++ /dev/null
@@ -1,115 +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.
- */
-properties only, shadow (6):
-  age              : 27
-  class            : class org.apache.freemarker.test.templatesuite.TemplateTestCase$TestMapBean
-  empty            : false
-  location         : San Francisco
-  luckyNumber      : 7
-  name             : Christopher
-
-properties only (6):
-  age              : 27
-  class            : class org.apache.freemarker.test.templatesuite.TemplateTestCase$TestMapBean
-  empty            : false
-  location         : San Francisco
-  luckyNumber      : 7
-  name             : Chris
-
-nothing, shadow (3):
-  age              : 27
-  location         : San Francisco
-  name             : Chris
-
-nothing (3):
-  age              : 27
-  location         : San Francisco
-  name             : Chris
-
-all, shadow (28):
-  age              : 27
-  class            : class org.apache.freemarker.test.templatesuite.TemplateTestCase$TestMapBean
-  clear            : UNKNOWN
-  clone            : UNKNOWN
-  containsKey      : UNKNOWN
-  containsValue    : UNKNOWN
-  empty            : false
-  entrySet         : UNKNOWN
-  equals           : UNKNOWN
-  get              : UNKNOWN
-  getClass         : UNKNOWN
-  getLuckyNumber   : UNKNOWN
-  getName          : UNKNOWN
-  hashCode         : UNKNOWN
-  isEmpty          : UNKNOWN
-  keySet           : UNKNOWN
-  location         : San Francisco
-  luckyNumber      : 7
-  name             : Christopher
-  notify           : UNKNOWN
-  notifyAll        : UNKNOWN
-  put              : UNKNOWN
-  putAll           : UNKNOWN
-  remove           : UNKNOWN
-  size             : UNKNOWN
-  toString         : UNKNOWN
-  values           : UNKNOWN
-  wait             : UNKNOWN
-
-all (28):
-  age              : 27
-  class            : class org.apache.freemarker.test.templatesuite.TemplateTestCase$TestMapBean
-  clear            : UNKNOWN
-  clone            : UNKNOWN
-  containsKey      : UNKNOWN
-  containsValue    : UNKNOWN
-  empty            : false
-  entrySet         : UNKNOWN
-  equals           : UNKNOWN
-  get              : UNKNOWN
-  getClass         : UNKNOWN
-  getLuckyNumber   : UNKNOWN
-  getName          : UNKNOWN
-  hashCode         : UNKNOWN
-  isEmpty          : UNKNOWN
-  keySet           : UNKNOWN
-  location         : San Francisco
-  luckyNumber      : 7
-  name             : Chris
-  notify           : UNKNOWN
-  notifyAll        : UNKNOWN
-  put              : UNKNOWN
-  putAll           : UNKNOWN
-  remove           : UNKNOWN
-  size             : UNKNOWN
-  toString         : UNKNOWN
-  values           : UNKNOWN
-  wait             : UNKNOWN
-
-simple map mode (3):
-  age              : 27
-  location         : San Francisco
-  name             : Chris
-
-
-String concatenation:
-  helloworld
-  helloworld
-  hellohello
-  worldworld



[07/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..f98f0f8
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperSingletonsTest.java
@@ -0,0 +1,689 @@
+/*
+ * 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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.lang.ref.Reference;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Version;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.test.util.TestUtil;
+
+import junit.framework.TestCase;
+
+public class DefaultObjectWrapperSingletonsTest extends TestCase {
+
+    public DefaultObjectWrapperSingletonsTest(String name) {
+        super(name);
+    }
+    
+    @Override
+    protected void setUp() throws Exception {
+        DefaultObjectWrapperBuilder.clearInstanceCache();
+    }
+
+    public void testBuilderEqualsAndHash() throws Exception {
+        assertEquals(Configuration.VERSION_3_0_0, new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).getIncompatibleImprovements());
+        try {
+            new DefaultObjectWrapperBuilder(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;
+        
+        builder1 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+        builder2 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+        assertEquals(builder1, builder2);
+        
+        builder1.setExposeFields(true);
+        assertNotEquals(builder1, builder2);
+        assertFalse(builder1.hashCode() == builder2.hashCode());
+        builder2.setExposeFields(true);
+        assertEquals(builder1, builder2);
+        assertTrue(builder1.hashCode() == builder2.hashCode());
+        
+        builder1.setExposureLevel(0);
+        assertNotEquals(builder1, builder2);
+        assertFalse(builder1.hashCode() == builder2.hashCode());
+        builder2.setExposureLevel(0);
+        assertEquals(builder1, builder2);
+        assertTrue(builder1.hashCode() == builder2.hashCode());
+        
+        builder1.setExposureLevel(1);
+        assertNotEquals(builder1, builder2);
+        assertFalse(builder1.hashCode() == builder2.hashCode());
+        builder2.setExposureLevel(1);
+        assertEquals(builder1, builder2);
+        assertTrue(builder1.hashCode() == builder2.hashCode());
+        
+        builder1.setDefaultDateType(TemplateDateModel.DATE);
+        assertNotEquals(builder1, builder2);
+        builder2.setDefaultDateType(TemplateDateModel.DATE);
+        assertEquals(builder1, builder2);
+        assertTrue(builder1.hashCode() == builder2.hashCode());
+        
+        builder1.setStrict(true);
+        assertNotEquals(builder1, builder2);
+        assertFalse(builder1.hashCode() == builder2.hashCode());
+        builder2.setStrict(true);
+        assertEquals(builder1, builder2);
+        assertTrue(builder1.hashCode() == builder2.hashCode());
+
+        builder1.setUseModelCache(true);
+        assertNotEquals(builder1, builder2);
+        assertFalse(builder1.hashCode() == builder2.hashCode());
+        builder2.setUseModelCache(true);
+        assertEquals(builder1, builder2);
+        assertTrue(builder1.hashCode() == builder2.hashCode());
+        
+        AlphabeticalMethodSorter ms = new AlphabeticalMethodSorter(true);
+        builder1.setMethodSorter(ms);
+        assertNotEquals(builder1, builder2);
+        builder2.setMethodSorter(ms);
+        assertEquals(builder1, builder2);
+        assertTrue(builder1.hashCode() == builder2.hashCode());
+
+        MethodAppearanceFineTuner maft = new MethodAppearanceFineTuner() {
+            @Override
+            public void process(DecisionInput in, Decision out) { }
+        };
+        builder1.setMethodAppearanceFineTuner(maft);
+        assertNotEquals(builder1, builder2);
+        builder2.setMethodAppearanceFineTuner(maft);
+        assertEquals(builder1, builder2);
+        assertTrue(builder1.hashCode() == builder2.hashCode());
+    }
+    
+    public void testDefaultObjectWrapperBuilderProducts() throws Exception {
+        List<DefaultObjectWrapper> hardReferences = new LinkedList<>();
+        
+        assertEquals(0, getDefaultObjectWrapperInstanceCacheSize());
+        
+        {
+            DefaultObjectWrapper ow = getDefaultObjectWrapperWithSetting(Configuration.VERSION_3_0_0, true);
+            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());
+            assertSame(ow, ow.getOuterIdentity());
+            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());
+            
+            hardReferences.add(ow);
+        }
+        
+        {
+            DefaultObjectWrapper ow = getDefaultObjectWrapperWithSetting(Configuration.VERSION_3_0_0, false);
+            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));
+            
+            hardReferences.add(ow);
+        }
+        
+        {
+            DefaultObjectWrapperBuilder factory = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            factory.setExposureLevel(DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY);
+            DefaultObjectWrapper ow1 = factory.build();
+            DefaultObjectWrapper ow2 = factory.build();
+            assertEquals(3, getDefaultObjectWrapperInstanceCacheSize());
+            assertSame(ow1, ow2);
+            
+            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());
+            assertSame(ow1, ow1.getOuterIdentity());
+            
+            hardReferences.add(ow1);
+        }
+        
+        {
+            DefaultObjectWrapperBuilder factory = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            factory.setExposeFields(true);
+            DefaultObjectWrapper ow1 = factory.build();
+            DefaultObjectWrapper ow2 = factory.build();
+            assertEquals(4, getDefaultObjectWrapperInstanceCacheSize());
+            assertSame(ow1, ow2);
+            
+            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);
+            factory.setStrict(true);
+            factory.setDefaultDateType(TemplateDateModel.DATETIME);
+            factory.setOuterIdentity(new SimpleObjectWrapper(Configuration.VERSION_3_0_0));
+            DefaultObjectWrapper ow = factory.build();
+            assertEquals(5, getDefaultObjectWrapperInstanceCacheSize());
+            assertTrue(ow.isStrict());
+            assertEquals(TemplateDateModel.DATETIME, ow.getDefaultDateType());
+            assertSame(SimpleObjectWrapper.class, ow.getOuterIdentity().getClass());
+            
+            hardReferences.add(ow);
+        }
+        
+        // Effect of reference and cache clearings:
+        {
+            DefaultObjectWrapper bw1 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+            assertEquals(5, getDefaultObjectWrapperInstanceCacheSize());
+            assertEquals(5, getDefaultObjectWrapperNonClearedInstanceCacheSize());
+            
+            clearDefaultObjectWrapperInstanceCacheReferences(false);
+            assertEquals(5, getDefaultObjectWrapperInstanceCacheSize());
+            assertEquals(0, getDefaultObjectWrapperNonClearedInstanceCacheSize());
+            
+            DefaultObjectWrapper bw2 = new DefaultObjectWrapperBuilder(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());
+            assertEquals(1, getDefaultObjectWrapperNonClearedInstanceCacheSize());
+            
+            clearDefaultObjectWrapperInstanceCacheReferences(true);
+            DefaultObjectWrapper bw3 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+            assertNotSame(bw2, bw3);
+            assertEquals(1, getDefaultObjectWrapperInstanceCacheSize());
+            assertEquals(1, getDefaultObjectWrapperNonClearedInstanceCacheSize());
+        }
+
+        {
+            DefaultObjectWrapperBuilder factory = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            factory.setUseModelCache(true);
+            DefaultObjectWrapper ow = factory.build();
+            assertTrue(ow.getUseModelCache());
+            assertEquals(2, getDefaultObjectWrapperInstanceCacheSize());
+            
+            hardReferences.add(ow);
+        }
+        
+        assertTrue(hardReferences.size() != 0);  // just to save it from GC until this line        
+    }
+    
+    private DefaultObjectWrapper getDefaultObjectWrapperWithSetting(Version ici, boolean useModelCache) {
+        DefaultObjectWrapperBuilder f = new DefaultObjectWrapperBuilder(ici);
+        f.setUseModelCache(useModelCache);
+        return f.build();
+    }
+
+    public void testMultipleTCCLs() {
+        List<DefaultObjectWrapper> hardReferences = new LinkedList<>();
+        
+        assertEquals(0, getDefaultObjectWrapperInstanceCacheSize());
+        
+        DefaultObjectWrapper bw1 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        assertEquals(1, getDefaultObjectWrapperInstanceCacheSize());
+        hardReferences.add(bw1);
+        
+        ClassLoader oldTCCL = Thread.currentThread().getContextClassLoader();
+        // Doesn't mater what, just be different from oldTCCL: 
+        ClassLoader newTCCL = oldTCCL == null ? getClass().getClassLoader() : null;
+        
+        DefaultObjectWrapper bw2;
+        Thread.currentThread().setContextClassLoader(newTCCL);
+        try {
+            bw2 = new DefaultObjectWrapperBuilder(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());
+        } finally {
+            Thread.currentThread().setContextClassLoader(oldTCCL);
+        }
+        
+        assertSame(bw1, new DefaultObjectWrapperBuilder(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());
+            
+            DefaultObjectWrapperBuilder bwb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            bwb.setExposeFields(true);
+            bw3 = bwb.build();
+            assertEquals(3, getDefaultObjectWrapperInstanceCacheSize());
+            hardReferences.add(bw3);
+        } finally {
+            Thread.currentThread().setContextClassLoader(oldTCCL);
+        }
+        
+        {
+            DefaultObjectWrapperBuilder bwb = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            bwb.setExposeFields(true);
+            DefaultObjectWrapper bw4 = bwb.build();
+            assertEquals(4, getDefaultObjectWrapperInstanceCacheSize());
+            assertNotSame(bw3, bw4);
+            hardReferences.add(bw4);
+        }
+        
+        assertTrue(hardReferences.size() != 0);  // just to save it from GC until this line        
+    }
+    
+    public void testClassInrospectorCache() throws TemplateModelException {
+        assertFalse(new DefaultObjectWrapper(Configuration.VERSION_3_0_0).isClassIntrospectionCacheRestricted());
+        assertTrue(new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0)
+                .build().isClassIntrospectionCacheRestricted());
+        
+        ClassIntrospectorBuilder.clearInstanceCache();
+        DefaultObjectWrapperBuilder.clearInstanceCache();
+        checkClassIntrospectorCacheSize(0);
+        
+        List<DefaultObjectWrapper> hardReferences = new LinkedList<>();
+        DefaultObjectWrapperBuilder builder;
+
+        {
+            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            
+            DefaultObjectWrapper bw1 = builder.build();
+            checkClassIntrospectorCacheSize(1);
+            
+            builder.setExposureLevel(DefaultObjectWrapper.EXPOSE_SAFE);  // this was already set to this
+            builder.setUseModelCache(true);  // this shouldn't matter for the introspection cache
+            DefaultObjectWrapper bw2 = builder.build();
+            checkClassIntrospectorCacheSize(1);
+            
+            assertSame(bw2.getClassIntrospector(), bw1.getClassIntrospector());
+            assertNotSame(bw1, bw2);
+            
+            // Wrapping tests:
+            assertFalse(exposesFields(bw1));
+            assertTrue(exposesProperties(bw1));
+            assertTrue(exposesMethods(bw1));
+            assertFalse(exposesUnsafe(bw1));
+            assertTrue(bw1.isClassIntrospectionCacheRestricted());
+            // Prevent introspection cache GC:
+            hardReferences.add(bw1);
+            hardReferences.add(bw2);
+        }
+        
+        {
+            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder.setExposeFields(true);
+            DefaultObjectWrapper ow = builder.build();
+            checkClassIntrospectorCacheSize(2);
+            // Wrapping tests:
+            assertTrue(exposesFields(ow));
+            assertTrue(exposesProperties(ow));
+            assertTrue(exposesMethods(ow));
+            assertFalse(exposesUnsafe(ow));
+            // Prevent introspection cache GC:
+            hardReferences.add(ow);
+        }
+
+        {
+            builder.setExposureLevel(DefaultObjectWrapper.EXPOSE_ALL);
+            DefaultObjectWrapper ow = builder.build();
+            checkClassIntrospectorCacheSize(3);
+            // Wrapping tests:
+            assertTrue(exposesFields(ow));
+            assertTrue(exposesProperties(ow));
+            assertTrue(exposesMethods(ow));
+            assertTrue(exposesUnsafe(ow));
+            // Prevent introspection cache GC:
+            hardReferences.add(ow);
+        }
+        
+        {
+            builder.setExposeFields(false);
+            DefaultObjectWrapper ow = builder.build();
+            checkClassIntrospectorCacheSize(4);
+            // Wrapping tests:
+            assertFalse(exposesFields(ow));
+            assertTrue(exposesProperties(ow));
+            assertTrue(exposesMethods(ow));
+            assertTrue(exposesUnsafe(ow));
+            // Prevent introspection cache GC:
+            hardReferences.add(ow);
+        }
+        
+        {
+            builder.setExposureLevel(DefaultObjectWrapper.EXPOSE_NOTHING);
+            DefaultObjectWrapper ow = builder.build();
+            checkClassIntrospectorCacheSize(5);
+            // Wrapping tests:
+            assertFalse(exposesFields(ow));
+            assertFalse(exposesProperties(ow));
+            assertFalse(exposesMethods(ow));
+            assertFalse(exposesUnsafe(ow));
+            // Prevent introspection cache GC:
+            hardReferences.add(ow);
+        }
+
+        {
+            builder.setExposeFields(true);
+            DefaultObjectWrapper ow = builder.build();
+            checkClassIntrospectorCacheSize(6);
+            // Wrapping tests:
+            assertTrue(exposesFields(ow));
+            assertFalse(exposesProperties(ow));
+            assertFalse(exposesMethods(ow));
+            assertFalse(exposesUnsafe(ow));
+            // Prevent introspection cache GC:
+            hardReferences.add(ow);
+        }
+
+        {
+            builder.setExposureLevel(DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY);
+            DefaultObjectWrapper ow = builder.build();
+            checkClassIntrospectorCacheSize(7);
+            // Wrapping tests:
+            assertTrue(exposesFields(ow));
+            assertTrue(exposesProperties(ow));
+            assertFalse(exposesMethods(ow));
+            assertFalse(exposesUnsafe(ow));
+            // Prevent introspection cache GC:
+            hardReferences.add(ow);
+        }
+        
+        {
+            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder.setUseModelCache(true);
+            builder.setExposeFields(false);
+            builder.setExposureLevel(DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY);
+            
+            DefaultObjectWrapper bw1 = builder.build();
+            checkClassIntrospectorCacheSize(8);
+            ClassIntrospector ci1 = bw1.getClassIntrospector();
+            
+            builder.setUseModelCache(false);  // Shouldn't mater for the ClassIntrospector
+            DefaultObjectWrapper bw2 = builder.build();
+            ClassIntrospector ci2 = bw2.getClassIntrospector();
+            checkClassIntrospectorCacheSize(8);
+            
+            assertSame(ci1, ci2);
+            assertNotSame(bw1, bw2);
+            
+            // Wrapping tests:
+            assertFalse(exposesFields(bw1));
+            assertTrue(exposesProperties(bw1));
+            assertFalse(exposesMethods(bw1));
+            assertFalse(exposesUnsafe(bw1));
+
+            // Prevent introspection cache GC:
+            hardReferences.add(bw1);
+            hardReferences.add(bw2);
+        }
+
+        // The ClassInrospector cache couldn't become cleared in reality otherwise:
+        DefaultObjectWrapperBuilder.clearInstanceCache();
+
+        clearClassIntrospectorInstanceCacheReferences(false);
+        checkClassIntrospectorCacheSize(8);
+        assertEquals(0, getClassIntrospectorNonClearedInstanceCacheSize());
+
+        {
+            builder.setExposeFields(false);
+            
+            DefaultObjectWrapper bw1 = builder.build();
+            checkClassIntrospectorCacheSize(8);
+            assertEquals(1, getClassIntrospectorNonClearedInstanceCacheSize());
+            ClassIntrospector ci1 = bw1.getClassIntrospector();
+            
+            builder.setUseModelCache(true);  // Shouldn't mater
+            DefaultObjectWrapper bw2 = builder.build();
+            ClassIntrospector ci2 = bw2.getClassIntrospector();
+            
+            assertSame(ci1, ci2);
+            assertNotSame(bw1, bw2);
+            
+            // Wrapping tests:
+            assertFalse(exposesFields(bw1));
+            assertTrue(exposesProperties(bw1));
+            assertFalse(exposesMethods(bw1));
+            assertFalse(exposesUnsafe(bw1));
+
+            // Prevent introspection cache GC:
+            hardReferences.add(bw1);
+            hardReferences.add(bw2);
+        }
+        
+        {
+            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            DefaultObjectWrapper ow = builder.build();
+            checkClassIntrospectorCacheSize(8);
+            assertEquals(2, getClassIntrospectorNonClearedInstanceCacheSize());
+            // Wrapping tests:
+            assertFalse(exposesFields(ow));
+            assertTrue(exposesProperties(ow));
+            assertTrue(exposesMethods(ow));
+            assertFalse(exposesUnsafe(ow));
+            // Prevent introspection cache GC:
+            hardReferences.add(ow);
+        }
+
+        clearClassIntrospectorInstanceCacheReferences(true);
+        checkClassIntrospectorCacheSize(8);
+        assertEquals(0, getClassIntrospectorNonClearedInstanceCacheSize());
+        
+        {
+            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder.setExposeFields(true);
+            DefaultObjectWrapper ow = builder.build();
+            checkClassIntrospectorCacheSize(1);
+            // Wrapping tests:
+            assertTrue(exposesFields(ow));
+            assertTrue(exposesProperties(ow));
+            assertTrue(exposesMethods(ow));
+            assertFalse(exposesUnsafe(ow));
+            // Prevent introspection cache GC:
+            hardReferences.add(ow);
+        }
+        
+        {
+            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() {
+                @Override
+                public void process(DecisionInput in, Decision out) {
+                }
+            });  // spoils ClassIntrospector() sharing
+
+            builder.setUseModelCache(false);
+            DefaultObjectWrapper bw1 = builder.build();
+            assertSame(bw1, builder.build());
+
+            builder.setUseModelCache(true);
+            DefaultObjectWrapper bw2 = builder.build();
+            checkClassIntrospectorCacheSize(1);
+            assertNotSame(bw1, bw2);
+            assertNotSame(bw1.getClassIntrospector(), bw2.getClassIntrospector());
+            assertTrue(bw1.isClassIntrospectionCacheRestricted());
+            assertTrue(bw2.isClassIntrospectionCacheRestricted());
+        }
+
+        {
+            builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
+            builder.setMethodAppearanceFineTuner(
+                    GetlessMethodsAsPropertyGettersRule.INSTANCE);  // doesn't spoils sharing
+
+            builder.setUseModelCache(false);
+            DefaultObjectWrapper bw1 = builder.build();
+            assertSame(bw1, builder.build());
+            checkClassIntrospectorCacheSize(2);
+            
+            builder.setUseModelCache(true);
+            DefaultObjectWrapper bw2 = builder.build();
+            checkClassIntrospectorCacheSize(2);
+            
+            assertNotSame(bw1, bw2);
+            assertSame(bw1.getClassIntrospector(), bw2.getClassIntrospector());  // !
+            assertTrue(bw2.isClassIntrospectionCacheRestricted());
+        }
+        
+        assertTrue(hardReferences.size() != 0);  // just to save it from GC until this line        
+    }
+    
+    private void checkClassIntrospectorCacheSize(int expectedSize) {
+        assertEquals(expectedSize, getClassIntrospectorInstanceCacheSize());
+    }
+
+    private void assertNotEquals(Object o1, Object o2) {
+        assertFalse(o1.equals(o2));
+    }
+    
+    public class C {
+        
+        public String foo = "FOO";
+        
+        public String getBar() {
+            return "BAR";
+        }
+        
+    }
+
+    private boolean exposesFields(DefaultObjectWrapper ow) throws TemplateModelException {
+        TemplateHashModel thm = (TemplateHashModel) ow.wrap(new C());
+        TemplateScalarModel r = (TemplateScalarModel) thm.get("foo");
+        if (r == null) return false;
+        assertEquals("FOO", r.getAsString());
+        return true;
+    }
+
+    private boolean exposesProperties(DefaultObjectWrapper ow) throws TemplateModelException {
+        TemplateHashModel thm = (TemplateHashModel) ow.wrap(new C());
+        TemplateScalarModel r = (TemplateScalarModel) thm.get("bar");
+        if (r == null) return false;
+        assertEquals("BAR", r.getAsString());
+        return true;
+    }
+
+    private boolean exposesMethods(DefaultObjectWrapper ow) throws TemplateModelException {
+        TemplateHashModel thm = (TemplateHashModel) ow.wrap(new C());
+        return thm.get("getBar") != null;
+    }
+
+    private boolean exposesUnsafe(DefaultObjectWrapper ow) throws TemplateModelException {
+        TemplateHashModel thm = (TemplateHashModel) ow.wrap(new C());
+        return thm.get("wait") != null;
+    }
+    
+    static int getClassIntrospectorInstanceCacheSize() {
+        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
+        synchronized (instanceCache) {
+            return instanceCache.size();
+        }
+    }
+
+    static int getClassIntrospectorNonClearedInstanceCacheSize() {
+        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
+        synchronized (instanceCache) {
+            int cnt = 0;
+            for (Iterator it = instanceCache.values().iterator(); it.hasNext(); ) {
+                if (((Reference) it.next()).get() != null) cnt++;
+            }
+            return cnt;
+        }
+    }
+    
+    static void clearClassIntrospectorInstanceCacheReferences(boolean enqueue) {
+        Map instanceCache = ClassIntrospectorBuilder.getInstanceCache();
+        synchronized (instanceCache) {
+            for (Iterator it = instanceCache.values().iterator(); it.hasNext(); ) {
+                Reference ref = ((Reference) it.next());
+                ref.clear();
+                if (enqueue) {
+                    ref.enqueue();
+                }
+            }
+        }
+    }
+
+    static int getDefaultObjectWrapperInstanceCacheSize() {
+        Map instanceCache = DefaultObjectWrapperBuilder.getInstanceCache();
+        synchronized (instanceCache) {
+            int size = 0; 
+            for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
+                size += ((Map) it1.next()).size();
+            }
+            return size;
+        }
+    }
+
+    static int getDefaultObjectWrapperNonClearedInstanceCacheSize() {
+        Map instanceCache = DefaultObjectWrapperBuilder.getInstanceCache();
+        synchronized (instanceCache) {
+            int cnt = 0;
+            for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
+                Map tcclScope = (Map) it1.next();
+                for (Iterator it2 = tcclScope.values().iterator(); it2.hasNext(); ) {
+                    if (((Reference) it2.next()).get() != null) cnt++;
+                }
+            }
+            return cnt;
+        }
+    }
+    
+    static void clearDefaultObjectWrapperInstanceCacheReferences(boolean enqueue) {
+        Map instanceCache = DefaultObjectWrapperBuilder.getInstanceCache();
+        synchronized (instanceCache) {
+            for (Iterator it1 = instanceCache.values().iterator(); it1.hasNext(); ) {
+                Map tcclScope = (Map) it1.next();
+                for (Iterator it2 = tcclScope.values().iterator(); it2.hasNext(); ) {
+                    Reference ref = ((Reference) it2.next());
+                    ref.clear();
+                    if (enqueue) {
+                        ref.enqueue();
+                    }
+                }
+            }
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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 48099a7..5b62a9b 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
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.Vector;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -77,9 +78,10 @@ import com.google.common.collect.ImmutableMap;
 
 public class DefaultObjectWrapperTest {
 
-    private final static DefaultObjectWrapper OW300 = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0)
+    private final static DefaultObjectWrapper OW = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0)
             .build();
 
+    // This will make sense if we will have multipe incompatibleImprovement versions.
     @Test
     public void testIncompatibleImprovementsVersionBreakPoints() throws Exception {
         List<Version> expected = new ArrayList<>();
@@ -135,22 +137,26 @@ public class DefaultObjectWrapperTest {
     @SuppressWarnings("boxing")
     @Test
     public void testBuilder() throws Exception {
-        for (boolean simpleMapWrapper : new boolean[] { true, false }) {
-            DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0);
-            builder.setSimpleMapWrapper(simpleMapWrapper); // Shouldn't mater
-            DefaultObjectWrapper bw = builder.build();
-            assertSame(bw, builder.build());
-            assertSame(bw.getClass(), DefaultObjectWrapper.class);
-            assertEquals(Configuration.VERSION_3_0_0, bw.getIncompatibleImprovements());
-            assertTrue(bw.isWriteProtected());
-            assertEquals(simpleMapWrapper, bw.isSimpleMapWrapper());
-
-            assertThat(bw.wrap(new HashMap()), instanceOf(DefaultMapAdapter.class));
-            assertThat(bw.wrap(new ArrayList()), instanceOf(DefaultListAdapter.class));
-            assertThat(bw.wrap(new String[] {}), instanceOf(DefaultArrayAdapter.class));
-            assertThat(bw.wrap(new HashSet()), instanceOf(DefaultNonListCollectionAdapter.class));
-            assertThat(bw.wrap(new PureIterable()), instanceOf(DefaultIterableAdapter.class));
-        }
+        DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(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 ow = builder.build();
+
+        assertThat(ow.wrap(new HashMap()), instanceOf(DefaultMapAdapter.class));
+        assertThat(ow.wrap(new ArrayList()), instanceOf(DefaultListAdapter.class));
+        assertThat(ow.wrap(new String[] {}), instanceOf(DefaultArrayAdapter.class));
+        assertThat(ow.wrap(new HashSet()), instanceOf(DefaultNonListCollectionAdapter.class));
+        assertThat(ow.wrap(new PureIterable()), instanceOf(DefaultIterableAdapter.class));
+        assertThat(ow.wrap(new Vector<>().iterator()), instanceOf(DefaultIteratorAdapter.class));
+        assertThat(ow.wrap(new Vector<>().elements()), instanceOf(DefaultEnumerationAdapter.class));
     }
     
     @Test
@@ -218,8 +224,8 @@ public class DefaultObjectWrapperTest {
 
     @SuppressWarnings("boxing")
     @Test
-    public void testRoundtripping() throws TemplateModelException, ClassNotFoundException {
-        DefaultObjectWrapper dow22 = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+    public void testCompositeValueWrapping() throws TemplateModelException, ClassNotFoundException {
+        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
 
         final Map hashMap = new HashMap();
         inintTestMap(hashMap);
@@ -234,13 +240,16 @@ public class DefaultObjectWrapperTest {
         linkedList.add("c");
         final int[] intArray = new int[] { 1, 2, 3 };
         final String[] stringArray = new String[] { "a", "b", "c" };
+        final PureIterable pureIterable = new PureIterable();
+        final HashSet hashSet = new HashSet();
 
-        assertRoundtrip(dow22, linkedHashMap, DefaultMapAdapter.class, LinkedHashMap.class, linkedHashMap.toString());
-        assertRoundtrip(dow22, treeMap, DefaultMapAdapter.class, TreeMap.class, treeMap.toString());
-        assertRoundtrip(dow22, gMap, DefaultMapAdapter.class, ImmutableMap.class, gMap.toString());
-        assertRoundtrip(dow22, linkedList, DefaultListAdapter.class, LinkedList.class, linkedList.toString());
-        assertRoundtrip(dow22, intArray, DefaultArrayAdapter.class, int[].class, null);
-        assertRoundtrip(dow22, stringArray, DefaultArrayAdapter.class, String[].class, null);
+        assertRoundtrip(ow, linkedHashMap, DefaultMapAdapter.class, LinkedHashMap.class, linkedHashMap.toString());
+        assertRoundtrip(ow, treeMap, DefaultMapAdapter.class, TreeMap.class, treeMap.toString());
+        assertRoundtrip(ow, gMap, DefaultMapAdapter.class, ImmutableMap.class, gMap.toString());
+        assertRoundtrip(ow, linkedList, DefaultListAdapter.class, LinkedList.class, linkedList.toString());
+        assertRoundtrip(ow, intArray, DefaultArrayAdapter.class, int[].class, null);
+        assertRoundtrip(ow, stringArray, DefaultArrayAdapter.class, String[].class, null);
+        assertRoundtrip(ow, hashSet, DefaultNonListCollectionAdapter.class, HashSet.class, hashSet.toString());
     }
 
     @SuppressWarnings("boxing")
@@ -260,7 +269,7 @@ public class DefaultObjectWrapperTest {
         testMap.put("d", Collections.singletonList("x"));
 
         {
-            TemplateHashModelEx hash = (TemplateHashModelEx) OW300.wrap(testMap);
+            TemplateHashModelEx hash = (TemplateHashModelEx) OW.wrap(testMap);
             assertEquals(4, hash.size());
             assertFalse(hash.isEmpty());
             assertNull(hash.get("e"));
@@ -276,7 +285,7 @@ public class DefaultObjectWrapperTest {
         }
 
         {
-            assertTrue(((TemplateHashModel) OW300.wrap(Collections.emptyMap())).isEmpty());
+            assertTrue(((TemplateHashModel) OW.wrap(Collections.emptyMap())).isEmpty());
         }
     }
 
@@ -290,14 +299,14 @@ public class DefaultObjectWrapperTest {
                 if (idx >= expectedItems.length) {
                     fail("Number of items is more than the expected " + expectedItems.length);
                 }
-                assertEquals(expectedItems[idx], OW300.unwrap(actualItem));
+                assertEquals(expectedItems[idx], OW.unwrap(actualItem));
                 if (i == 1) {
                     // In the 2nd round we also test with two iterators in parallel.
                     // This 2nd iterator is also special in that its hasNext() is never called.
                     if (it2 == null) {
                         it2 = coll.iterator();
                     }
-                    assertEquals(expectedItems[idx], OW300.unwrap(it2.next()));
+                    assertEquals(expectedItems[idx], OW.unwrap(it2.next()));
                 }
                 idx++;
             }
@@ -317,7 +326,7 @@ public class DefaultObjectWrapperTest {
             testList.add("c");
             testList.add(new String[] { "x" });
 
-            TemplateSequenceModel seq = (TemplateSequenceModel) OW300.wrap(testList);
+            TemplateSequenceModel seq = (TemplateSequenceModel) OW.wrap(testList);
             assertTrue(seq instanceof DefaultListAdapter);
             assertFalse(seq instanceof TemplateCollectionModel); // Maybe changes at 2.4.0
             assertEquals(4, seq.size());
@@ -337,7 +346,7 @@ public class DefaultObjectWrapperTest {
             testList.add(null);
             testList.add("c");
 
-            TemplateSequenceModel seq = (TemplateSequenceModel) OW300.wrap(testList);
+            TemplateSequenceModel seq = (TemplateSequenceModel) OW.wrap(testList);
             assertTrue(seq instanceof DefaultListAdapter);
             assertTrue(seq instanceof TemplateCollectionModel); // Maybe changes at 2.4.0
             assertEquals(3, seq.size());
@@ -364,16 +373,16 @@ public class DefaultObjectWrapperTest {
 
     @Test
     public void testArrayAdapterTypes() throws TemplateModelException {
-        assertArrayAdapterClass("Object", OW300.wrap(new Object[] {}));
-        assertArrayAdapterClass("Object", OW300.wrap(new String[] {}));
-        assertArrayAdapterClass("byte", OW300.wrap(new byte[] {}));
-        assertArrayAdapterClass("short", OW300.wrap(new short[] {}));
-        assertArrayAdapterClass("int", OW300.wrap(new int[] {}));
-        assertArrayAdapterClass("long", OW300.wrap(new long[] {}));
-        assertArrayAdapterClass("float", OW300.wrap(new float[] {}));
-        assertArrayAdapterClass("double", OW300.wrap(new double[] {}));
-        assertArrayAdapterClass("boolean", OW300.wrap(new boolean[] {}));
-        assertArrayAdapterClass("char", OW300.wrap(new char[] {}));
+        assertArrayAdapterClass("Object", OW.wrap(new Object[] {}));
+        assertArrayAdapterClass("Object", OW.wrap(new String[] {}));
+        assertArrayAdapterClass("byte", OW.wrap(new byte[] {}));
+        assertArrayAdapterClass("short", OW.wrap(new short[] {}));
+        assertArrayAdapterClass("int", OW.wrap(new int[] {}));
+        assertArrayAdapterClass("long", OW.wrap(new long[] {}));
+        assertArrayAdapterClass("float", OW.wrap(new float[] {}));
+        assertArrayAdapterClass("double", OW.wrap(new double[] {}));
+        assertArrayAdapterClass("boolean", OW.wrap(new boolean[] {}));
+        assertArrayAdapterClass("char", OW.wrap(new char[] {}));
     }
 
     private void assertArrayAdapterClass(String adapterCompType, TemplateModel adaptedArray) {
@@ -388,7 +397,7 @@ public class DefaultObjectWrapperTest {
         {
             final String[] testArray = new String[] { "a", null, "c" };
 
-            TemplateSequenceModel seq = (TemplateSequenceModel) OW300.wrap(testArray);
+            TemplateSequenceModel seq = (TemplateSequenceModel) OW.wrap(testArray);
             assertEquals(3, seq.size());
             assertNull(seq.get(-1));
             assertEquals("a", ((TemplateScalarModel) seq.get(0)).getAsString());
@@ -399,7 +408,7 @@ public class DefaultObjectWrapperTest {
 
         {
             final int[] testArray = new int[] { 11, 22 };
-            TemplateSequenceModel seq = (TemplateSequenceModel) OW300.wrap(testArray);
+            TemplateSequenceModel seq = (TemplateSequenceModel) OW.wrap(testArray);
             assertEquals(2, seq.size());
             assertNull(seq.get(-1));
             assertEqualsAndSameClass(Integer.valueOf(11), ((TemplateNumberModel) seq.get(0)).getAsNumber());
@@ -409,7 +418,7 @@ public class DefaultObjectWrapperTest {
 
         {
             final double[] testArray = new double[] { 11, 22 };
-            TemplateSequenceModel seq = (TemplateSequenceModel) OW300.wrap(testArray);
+            TemplateSequenceModel seq = (TemplateSequenceModel) OW.wrap(testArray);
             assertEquals(2, seq.size());
             assertNull(seq.get(-1));
             assertEqualsAndSameClass(Double.valueOf(11), ((TemplateNumberModel) seq.get(0)).getAsNumber());
@@ -419,7 +428,7 @@ public class DefaultObjectWrapperTest {
 
         {
             final boolean[] testArray = new boolean[] { true, false };
-            TemplateSequenceModel seq = (TemplateSequenceModel) OW300.wrap(testArray);
+            TemplateSequenceModel seq = (TemplateSequenceModel) OW.wrap(testArray);
             assertEquals(2, seq.size());
             assertNull(seq.get(-1));
             assertEqualsAndSameClass(Boolean.valueOf(true), ((TemplateBooleanModel) seq.get(0)).getAsBoolean());
@@ -429,7 +438,7 @@ public class DefaultObjectWrapperTest {
 
         {
             final char[] testArray = new char[] { 'a', 'b' };
-            TemplateSequenceModel seq = (TemplateSequenceModel) OW300.wrap(testArray);
+            TemplateSequenceModel seq = (TemplateSequenceModel) OW.wrap(testArray);
             assertEquals(2, seq.size());
             assertNull(seq.get(-1));
             assertEquals("a", ((TemplateScalarModel) seq.get(0)).getAsString());
@@ -476,25 +485,25 @@ public class DefaultObjectWrapperTest {
             set.add("a");
             set.add("b");
             set.add("c");
-            TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW300.wrap(set);
+            TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW.wrap(set);
             assertTrue(coll instanceof DefaultNonListCollectionAdapter);
             assertEquals(3, coll.size());
             assertFalse(coll.isEmpty());
             assertCollectionTMEquals(coll, "a", "b", "c");
 
-            assertTrue(coll.contains(OW300.wrap("a")));
-            assertTrue(coll.contains(OW300.wrap("b")));
-            assertTrue(coll.contains(OW300.wrap("c")));
-            assertTrue(coll.contains(OW300.wrap("c")));
-            assertFalse(coll.contains(OW300.wrap("d")));
+            assertTrue(coll.contains(OW.wrap("a")));
+            assertTrue(coll.contains(OW.wrap("b")));
+            assertTrue(coll.contains(OW.wrap("c")));
+            assertTrue(coll.contains(OW.wrap("c")));
+            assertFalse(coll.contains(OW.wrap("d")));
             try {
-                assertFalse(coll.contains(OW300.wrap(1)));
+                assertFalse(coll.contains(OW.wrap(1)));
                 fail();
             } catch (TemplateModelException e) {
                 assertThat(e.getMessage(), containsString("Integer"));
             }
 
-            assertRoundtrip(OW300, set, DefaultNonListCollectionAdapter.class, TreeSet.class, "[a, b, c]");
+            assertRoundtrip(OW, set, DefaultNonListCollectionAdapter.class, TreeSet.class, "[a, b, c]");
             
             assertSizeThroughAPIModel(3, coll);
         }
@@ -504,24 +513,24 @@ public class DefaultObjectWrapperTest {
             final List<String> list = Collections.singletonList("b");
             set.add(list);
             set.add(null);
-            TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW300.wrap(set);
+            TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW.wrap(set);
             TemplateModelIterator it = coll.iterator();
             final TemplateModel tm1 = it.next();
-            Object obj1 = OW300.unwrap(tm1);
+            Object obj1 = OW.unwrap(tm1);
             final TemplateModel tm2 = it.next();
-            Object obj2 = OW300.unwrap(tm2);
+            Object obj2 = OW.unwrap(tm2);
             assertTrue(obj1 == null || obj2 == null);
             assertTrue(obj1 != null && obj1.equals(list) || obj2 != null && obj2.equals(list));
             assertTrue(tm1 instanceof DefaultListAdapter || tm2 instanceof DefaultListAdapter);
 
             List similarList = new ArrayList();
             similarList.add("b");
-            assertTrue(coll.contains(OW300.wrap(similarList)));
-            assertTrue(coll.contains(OW300.wrap(null)));
-            assertFalse(coll.contains(OW300.wrap("a")));
-            assertFalse(coll.contains(OW300.wrap(1)));
+            assertTrue(coll.contains(OW.wrap(similarList)));
+            assertTrue(coll.contains(OW.wrap(null)));
+            assertFalse(coll.contains(OW.wrap("a")));
+            assertFalse(coll.contains(OW.wrap(1)));
 
-            assertRoundtrip(OW300, set, DefaultNonListCollectionAdapter.class, HashSet.class, "[" + obj1 + ", "
+            assertRoundtrip(OW, set, DefaultNonListCollectionAdapter.class, HashSet.class, "[" + obj1 + ", "
                     + obj2 + "]");
         }
     }
@@ -531,14 +540,14 @@ public class DefaultObjectWrapperTest {
     public void testCollectionAdapterOutOfBounds() throws TemplateModelException {
         Set set = Collections.singleton(123);
 
-        TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW300.wrap(set);
+        TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW.wrap(set);
         TemplateModelIterator it = coll.iterator();
 
         for (int i = 0; i < 3; i++) {
             assertTrue(it.hasNext());
         }
 
-        assertEquals(123, OW300.unwrap(it.next()));
+        assertEquals(123, OW.unwrap(it.next()));
 
         for (int i = 0; i < 3; i++) {
             assertFalse(it.hasNext());
@@ -556,7 +565,7 @@ public class DefaultObjectWrapperTest {
         Set set = new HashSet();
         set.add(null);
 
-        TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW300.wrap(set);
+        TemplateCollectionModelEx coll = (TemplateCollectionModelEx) OW.wrap(set);
         assertEquals(1, coll.size());
         assertFalse(coll.isEmpty());
         assertNull(coll.iterator().next());
@@ -566,18 +575,18 @@ public class DefaultObjectWrapperTest {
     public void testIteratorWrapping() throws TemplateModelException, ClassNotFoundException {
         final List<String> list = ImmutableList.of("a", "b", "c");
         Iterator<String> it = list.iterator();
-        TemplateCollectionModel coll = (TemplateCollectionModel) OW300.wrap(it);
+        TemplateCollectionModel coll = (TemplateCollectionModel) OW.wrap(it);
 
-        assertRoundtrip(OW300, coll, DefaultIteratorAdapter.class, Iterator.class, null);
+        assertRoundtrip(OW, coll, DefaultIteratorAdapter.class, Iterator.class, null);
 
         TemplateModelIterator itIt = coll.iterator();
         TemplateModelIterator itIt2 = coll.iterator(); // used later
         assertTrue(itIt.hasNext());
-        assertEquals("a", OW300.unwrap(itIt.next()));
+        assertEquals("a", OW.unwrap(itIt.next()));
         assertTrue(itIt.hasNext());
-        assertEquals("b", OW300.unwrap(itIt.next()));
+        assertEquals("b", OW.unwrap(itIt.next()));
         assertTrue(itIt.hasNext());
-        assertEquals("c", OW300.unwrap(itIt.next()));
+        assertEquals("c", OW.unwrap(itIt.next()));
         assertFalse(itIt.hasNext());
         try {
             itIt.next();
@@ -614,20 +623,20 @@ public class DefaultObjectWrapperTest {
         Map sortedMapC = new TreeMap<>();
         sortedMapC.put('a', 1);
         
-        assertEquals(1, OW300.unwrap(((TemplateHashModel) OW300.wrap(hashMapS)).get("a")));
-        assertEquals(1, OW300.unwrap(((TemplateHashModel) OW300.wrap(hashMapC)).get("a")));
-        assertEquals(1, OW300.unwrap(((TemplateHashModel) OW300.wrap(sortedMapS)).get("a")));
+        assertEquals(1, OW.unwrap(((TemplateHashModel) OW.wrap(hashMapS)).get("a")));
+        assertEquals(1, OW.unwrap(((TemplateHashModel) OW.wrap(hashMapC)).get("a")));
+        assertEquals(1, OW.unwrap(((TemplateHashModel) OW.wrap(sortedMapS)).get("a")));
         try {
-            ((TemplateHashModel) OW300.wrap(sortedMapC)).get("a");
+            ((TemplateHashModel) OW.wrap(sortedMapC)).get("a");
         } catch (TemplateModelException e) {
             assertThat(e.getMessage(), containsStringIgnoringCase("String key"));
         }
         
-        assertNull(((TemplateHashModel) OW300.wrap(hashMapS)).get("b"));
-        assertNull(((TemplateHashModel) OW300.wrap(hashMapC)).get("b"));
-        assertNull(((TemplateHashModel) OW300.wrap(sortedMapS)).get("b"));
+        assertNull(((TemplateHashModel) OW.wrap(hashMapS)).get("b"));
+        assertNull(((TemplateHashModel) OW.wrap(hashMapC)).get("b"));
+        assertNull(((TemplateHashModel) OW.wrap(sortedMapS)).get("b"));
         try {
-            ((TemplateHashModel) OW300.wrap(sortedMapC)).get("b");
+            ((TemplateHashModel) OW.wrap(sortedMapC)).get("b");
         } catch (TemplateModelException e) {
             assertThat(e.getMessage(), containsStringIgnoringCase("String key"));
         }
@@ -639,7 +648,7 @@ public class DefaultObjectWrapperTest {
         
         String listingFTL = "<#list value as x>${x}<#sep>, </#list>";
         
-        DefaultObjectWrapper ow = OW300;
+        DefaultObjectWrapper ow = OW;
         TemplateModel tm = ow.wrap(iterable);
         assertThat(tm, instanceOf(TemplateCollectionModel.class));
         TemplateCollectionModel iterableTM = (TemplateCollectionModel) tm;
@@ -661,7 +670,7 @@ public class DefaultObjectWrapperTest {
             }
         }
 
-        assertTemplateOutput(OW300, iterable, listingFTL, "a, b, c");
+        assertTemplateOutput(OW, iterable, listingFTL, "a, b, c");
     }
     
     @Test
@@ -671,9 +680,27 @@ public class DefaultObjectWrapperTest {
         InputSource is = new InputSource();
         is.setCharacterStream(new StringReader("<doc><sub a='1' /></doc>"));
         Document doc = db.parse(is);        
-        assertTrue(OW300.wrap(doc) instanceof TemplateNodeModel);
+        assertTrue(OW.wrap(doc) instanceof TemplateNodeModel);
     }
-    
+
+    @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"));
+    }
+
     private void assertSizeThroughAPIModel(int expectedSize, TemplateModel normalModel) throws TemplateModelException {
         if (!(normalModel instanceof TemplateModelWithAPISupport)) {
             fail(); 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..ec16f54
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithShortedMethods.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.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/051a0822/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
new file mode 100644
index 0000000..a10247d
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperWithSortedMethods.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.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/051a0822/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
new file mode 100644
index 0000000..b608cda
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/EnumModelsTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import java.util.ArrayList;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class EnumModelsTest {
+    
+    @Test
+    public void modelCaching() throws Exception {
+        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_3_0_0);
+        TemplateHashModel enums = ow.getEnumModels();
+        TemplateHashModel e = (TemplateHashModel) enums.get(E.class.getName());
+        assertNotNull(e);
+        assertNotNull(e.get("A"));
+        assertNotNull(e.get("B"));
+        assertNull(e.get("C"));
+
+        try {
+            enums.get("no.such.ClassExists");
+            fail();
+        } catch (TemplateModelException ex) {
+            assertTrue(ex.getCause() instanceof ClassNotFoundException);
+        }
+        
+        TemplateModel a = e.get("A");
+        assertTrue(a instanceof TemplateScalarModel);
+        assertTrue(a instanceof TemplateHashModel);
+        assertEquals(((TemplateScalarModel) a).getAsString(), "ts:A");
+        TemplateMethodModelEx nameMethod = (TemplateMethodModelEx) ((TemplateHashModel) a).get("name");
+        assertEquals(((TemplateScalarModel) nameMethod.exec(new ArrayList())).getAsString(), "A");
+        
+        assertSame(e, enums.get(E.class.getName()));
+        
+        ow.clearClassIntrospecitonCache();
+        TemplateHashModel eAfterClean = (TemplateHashModel) enums.get(E.class.getName());
+        assertNotSame(e, eAfterClean);
+        assertSame(eAfterClean, enums.get(E.class.getName()));
+        assertNotNull(eAfterClean.get("A"));
+        assertNotNull(eAfterClean.get("B"));
+        assertNull(eAfterClean.get("C"));
+    }
+    
+    public enum E {
+        A, B;
+
+        @Override
+        public String toString() {
+            return "ts:" + super.toString();
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..3bd0363
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+import java.util.Date;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.TemplateHTMLOutputModel;
+import org.junit.Test;
+
+public class ErrorMessagesTest {
+
+    @Test
+    public void getterMessage() throws TemplateModelException {
+        DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        TemplateHashModel thm= (TemplateHashModel) ow.wrap(new TestBean());
+        
+        try {
+            thm.get("foo");
+        } catch (TemplateModelException e) {
+            e.printStackTrace();
+            final String msg = e.getMessage();
+            assertThat(msg, containsString("\"foo\""));
+            assertThat(msg, containsString("existing sub-variable"));
+        }
+        assertNull(thm.get("bar"));
+    }
+    
+    @Test
+    public void markupOutputParameter() throws Exception {
+        TemplateHTMLOutputModel html = HTMLOutputFormat.INSTANCE.fromMarkup("<p>a");
+
+        DefaultObjectWrapper ow = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build();
+        TemplateHashModel thm = (TemplateHashModel) ow.wrap(new TestBean());
+        
+        {
+            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get("m1");
+            try {
+                m.exec(Collections.singletonList(html));
+                fail();
+            } catch (TemplateModelException e) {
+                assertThat(e.getMessage(), allOf(
+                        containsString("String"), containsString("convert"), containsString("markup_output"),
+                        containsString("Tip:"), containsString("?markup_string")));
+            }
+        }
+        
+        {
+            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get("m2");
+            try {
+                m.exec(Collections.singletonList(html));
+                fail();
+            } catch (TemplateModelException e) {
+                assertThat(e.getMessage(), allOf(
+                        containsString("Date"), containsString("convert"), containsString("markup_output"),
+                        not(containsString("?markup_string"))));
+            }
+        }
+        
+        for (String methodName : new String[] { "mOverloaded", "mOverloaded3" }) {
+            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get(methodName);
+            try {
+                m.exec(Collections.singletonList(html));
+                fail();
+            } catch (TemplateModelException e) {
+                assertThat(e.getMessage(), allOf(
+                        containsString("No compatible overloaded"),
+                        containsString("String"), containsString("markup_output"),
+                        containsString("Tip:"), containsString("?markup_string")));
+            }
+        }
+        
+        {
+            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get("mOverloaded2");
+            try {
+                m.exec(Collections.singletonList(html));
+                fail();
+            } catch (TemplateModelException e) {
+                assertThat(e.getMessage(), allOf(
+                        containsString("No compatible overloaded"),
+                        containsString("Integer"), containsString("markup_output"),
+                        not(containsString("?markup_string"))));
+            }
+        }
+        
+        {
+            TemplateMethodModelEx m = (TemplateMethodModelEx) thm.get("mOverloaded4");
+            Object r = m.exec(Collections.singletonList(html));
+            if (r instanceof TemplateScalarModel) {
+                r = ((TemplateScalarModel) r).getAsString();
+            }
+            assertEquals("<p>a", r);
+        }
+    }
+    
+    public static class TestBean {
+        
+        public String getFoo() {
+            throw new RuntimeException("Dummy");
+        }
+        
+        public void m1(String s) {
+            // nop
+        }
+
+        public void m2(Date s) {
+            // nop
+        }
+
+        public void mOverloaded(String s) {
+            // nop
+        }
+
+        public void mOverloaded(Date d) {
+            // nop
+        }
+
+        public void mOverloaded2(Integer n) {
+            // nop
+        }
+
+        public void mOverloaded2(Date d) {
+            // nop
+        }
+
+        public void mOverloaded3(String... s) {
+            // nop
+        }
+
+        public void mOverloaded3(Date d) {
+            // nop
+        }
+        
+        public String mOverloaded4(String s) {
+            return s;
+        }
+
+        public String mOverloaded4(TemplateHTMLOutputModel s) throws TemplateModelException {
+            return s.getOutputFormat().getMarkupString(s);
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/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
new file mode 100644
index 0000000..dfd554d
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+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);
+        checkIfProperlyWrapped(ow.wrap(new C()));
+    }
+    
+    private void checkIfProperlyWrapped(TemplateModel tm) throws TemplateModelException {
+        TemplateHashModel thm = (TemplateHashModel) tm;
+        assertEquals("v1", ((TemplateScalarModel) thm.get("v1")).getAsString());
+        assertEquals("v2()", ((TemplateScalarModel) thm.get("v2")).getAsString());
+        assertEquals("getV3()", ((TemplateScalarModel) thm.get("v3")).getAsString());
+        assertTrue(thm.get("getV3") instanceof TemplateMethodModelEx);
+    }
+    
+    static public class C {
+        
+        public String v1 = "v1";
+
+        public String v2 = "v2";
+        public String v2() { return "v2()"; }
+        
+        public String v3() { return "v3()"; }
+        public String getV3() { return "getV3()"; }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/GetlessMethodsAsPropertyGettersRule.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/GetlessMethodsAsPropertyGettersRule.java b/src/test/java/org/apache/freemarker/core/model/impl/GetlessMethodsAsPropertyGettersRule.java
new file mode 100644
index 0000000..d62d355
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/GetlessMethodsAsPropertyGettersRule.java
@@ -0,0 +1,67 @@
+/*
+ * 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.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+
+class GetlessMethodsAsPropertyGettersRule implements MethodAppearanceFineTuner, SingletonCustomizer {
+    
+    static final GetlessMethodsAsPropertyGettersRule INSTANCE = new GetlessMethodsAsPropertyGettersRule();
+    
+    // Can't be constructed from outside
+    private GetlessMethodsAsPropertyGettersRule() { }
+
+    @Override
+    public void process(
+            DecisionInput in, Decision out) {
+        legacyProcess(in.getContainingClass(), in.getMethod(), out);
+    }
+
+    /** This only exists as the tests need to call this through the deprecated method too. */
+    public void legacyProcess(
+            Class clazz, Method m, Decision decision) {
+        if (m.getDeclaringClass() != Object.class
+                && m.getReturnType() != void.class
+                && m.getParameterTypes().length == 0) {
+            String mName = m.getName();
+            if (!looksLikePropertyReadMethod(mName)) {
+                decision.setExposeMethodAs(null);
+                try {
+                    decision.setExposeAsProperty(new PropertyDescriptor(
+                            mName, clazz, mName, null));
+                } catch (IntrospectionException e) {  // Won't happen...
+                    throw new RuntimeException(e); 
+                }
+            }
+        }
+    }
+    
+    private static boolean looksLikePropertyReadMethod(String name) {
+        final int verbEnd;
+        if (name.startsWith("get")) verbEnd = 3;
+        else if (name.startsWith("is")) verbEnd = 2;
+        else return false;
+        
+        return name.length() == verbEnd || Character.isUpperCase(name.charAt(verbEnd));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/IsApplicableTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/IsApplicableTest.java b/src/test/java/org/apache/freemarker/core/model/impl/IsApplicableTest.java
new file mode 100644
index 0000000..e7c473b
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/IsApplicableTest.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("boxing")
+public class IsApplicableTest extends TestCase {
+
+    public IsApplicableTest(String name) {
+        super(name);
+    }
+    
+    public void testSingle() {
+        ArgumentTypes ats = new ArgumentTypes(new Object[] { new Object() });
+        assertApplicable(ats, Object.class);
+        assertNotApplicable(ats, String.class);
+        assertNotApplicable(ats, CharSequence.class);
+        assertNotApplicable(ats, Integer.class);
+        assertNotApplicable(ats, Integer.TYPE);
+        
+        ats = new ArgumentTypes(new Object[] { "" });
+        assertApplicable(ats, Object.class);
+        assertApplicable(ats, CharSequence.class);
+        assertApplicable(ats, String.class);
+        assertNotApplicable(ats, Integer.class);
+        assertNotApplicable(ats, Integer.TYPE);
+
+        ats = new ArgumentTypes(new Object[] { 1 });
+        assertApplicable(ats, Object.class);
+        assertNotApplicable(ats, CharSequence.class);
+        assertNotApplicable(ats, String.class);
+        assertNotApplicable(ats, Short.class);
+        assertNotApplicable(ats, Short.TYPE);
+        assertApplicable(ats, Integer.class);
+        assertApplicable(ats, Integer.TYPE);
+        assertApplicable(ats, Float.class);
+        assertApplicable(ats, Float.TYPE);
+        assertApplicable(ats, Double.class);
+        assertApplicable(ats, Double.TYPE);
+        assertApplicable(ats, BigDecimal.class);
+        assertApplicable(ats, BigInteger.class);
+
+        ats = new ArgumentTypes(new Object[] { new OverloadedNumberUtil.IntegerOrByte(1, (byte) 1) });
+        assertApplicable(ats, Object.class);
+        assertNotApplicable(ats, CharSequence.class);
+        assertNotApplicable(ats, String.class);
+        assertApplicable(ats, Short.class);
+        assertApplicable(ats, Short.TYPE);
+        assertApplicable(ats, Integer.class);
+        assertApplicable(ats, Integer.TYPE);
+        assertApplicable(ats, Float.class);
+        assertApplicable(ats, Float.TYPE);
+        assertApplicable(ats, Double.class);
+        assertApplicable(ats, Double.TYPE);
+        assertApplicable(ats, BigDecimal.class);
+        assertApplicable(ats, BigInteger.class);
+        
+        ats = new ArgumentTypes(new Object[] { 1.0f });
+        assertApplicable(ats, Object.class);
+        assertNotApplicable(ats, CharSequence.class);
+        assertNotApplicable(ats, String.class);
+        assertNotApplicable(ats, Integer.class);
+        assertNotApplicable(ats, Integer.TYPE);
+        assertApplicable(ats, Float.class);
+        assertApplicable(ats, Float.TYPE);
+        assertApplicable(ats, Double.class);
+        assertApplicable(ats, Double.TYPE);
+        assertApplicable(ats, BigDecimal.class);
+        assertNotApplicable(ats, BigInteger.class);
+        
+        ats = new ArgumentTypes(new Object[] { null });
+        assertApplicable(ats, Object.class);
+        assertApplicable(ats, String.class);
+        assertApplicable(ats, Integer.class);
+        assertNotApplicable(ats, Integer.TYPE);
+        assertNotApplicable(ats, Boolean.TYPE);
+        assertNotApplicable(ats, Object.class, Object.class);
+        assertNotApplicable(ats);
+    }
+    
+    public void testMulti() {
+        ArgumentTypes ats = new ArgumentTypes(new Object[] { new Object(), "", 1, true });
+        assertApplicable(ats, Object.class, Object.class, Object.class, Object.class);
+        assertApplicable(ats, Object.class, String.class, Number.class, Boolean.class);
+        assertApplicable(ats, Object.class, CharSequence.class, Integer.class, Serializable.class);
+        assertApplicable(ats, Object.class, Comparable.class, Integer.TYPE, Serializable.class);
+        assertNotApplicable(ats, Object.class, String.class, Number.class, Number.class);
+        assertNotApplicable(ats, Object.class, StringBuilder.class, Number.class, Boolean.class);
+        assertNotApplicable(ats, int.class, Object.class, Object.class, Object.class);
+        assertNotApplicable(ats, Object.class, Object.class, Object.class);
+        assertNotApplicable(ats, Object.class, Object.class, Object.class, Object.class, Object.class);
+    }    
+
+    public void testNoParam() {
+        ArgumentTypes ats = new ArgumentTypes(new Object[] { });
+        assertApplicable(ats);
+        assertNotApplicable(ats, Object.class);
+    }
+
+    public void testVarags() {
+        Object[][] argLists = new Object[][] {
+            new Object[] { "", 1, 2, 3 },
+            new Object[] { "", 1, (byte) 2, 3 },
+            new Object[] { "", 1},
+            new Object[] { "" },
+        };
+        for (Object[] args : argLists) {
+            ArgumentTypes ats = new ArgumentTypes(args);
+            assertApplicable(ats, true, String.class, int[].class);
+            assertApplicable(ats, true, String.class, Integer[].class);
+            assertApplicable(ats, true, Object.class, Comparable[].class);
+            assertApplicable(ats, true, Object.class, Object[].class);
+            assertNotApplicable(ats, true, StringBuilder.class, int[].class);
+            if (args.length > 1) {
+                assertNotApplicable(ats, true, String.class, String[].class);
+            } else {
+                assertApplicable(ats, true, String.class, String[].class);
+            }
+        }
+    }
+    
+    private void assertNotApplicable(ArgumentTypes ats, Class... paramTypes) {
+        assertNotApplicable(ats, false, paramTypes);
+    }
+    
+    private void assertNotApplicable(ArgumentTypes ats, boolean varargs, Class... paramTypes) {
+        List tested = new ArrayList();
+        tested.add(new ReflectionCallableMemberDescriptor((Method) null, paramTypes));
+        if (ats.getApplicables(tested, varargs).size() != 0) {
+            fail("Parameter types were applicable");
+        }
+    }
+
+    private void assertApplicable(ArgumentTypes ats, Class<?>... paramTypes) {
+        assertApplicable(ats, false, paramTypes);
+    }
+    
+    private void assertApplicable(ArgumentTypes ats, boolean varargs, Class<?>... paramTypes) {
+        List tested = new ArrayList();
+        tested.add(new ReflectionCallableMemberDescriptor((Method) null, paramTypes));
+        if (ats.getApplicables(tested, varargs).size() != 1) {
+            fail("Parameter types weren't applicable");
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/test/java/org/apache/freemarker/core/model/impl/IsMoreSpecificParameterTypeTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/model/impl/IsMoreSpecificParameterTypeTest.java b/src/test/java/org/apache/freemarker/core/model/impl/IsMoreSpecificParameterTypeTest.java
new file mode 100644
index 0000000..fc45156
--- /dev/null
+++ b/src/test/java/org/apache/freemarker/core/model/impl/IsMoreSpecificParameterTypeTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+public class IsMoreSpecificParameterTypeTest extends TestCase {
+
+    public IsMoreSpecificParameterTypeTest(String name) {
+        super(name);
+    }
+    
+    public void testFixed() {
+        assertEquals(1, _MethodUtil.isMoreOrSameSpecificParameterType(String.class, String.class, true, 0));
+        assertEquals(1, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, int.class, true, 0));
+        
+        assertEquals(2, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Integer.class, true, 0));
+        assertEquals(2, _MethodUtil.isMoreOrSameSpecificParameterType(boolean.class, Boolean.class, true, 0));
+        
+        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, long.class, true, 0));
+        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, double.class, true, 0));
+        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Long.class, true, 0));
+        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Double.class, true, 0));
+        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Long.class, true, 0));
+        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Double.class, true, 0));
+
+        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(HashMap.class, Map.class, true, 0));
+        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(String.class, CharSequence.class, true, 0));
+        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Number.class, true, 0));
+        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Number.class, true, 0));
+        
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Map.class, String.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, int.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Boolean.class, boolean.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, boolean.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Boolean.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, String.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, BigDecimal.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Long.class, Integer.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(long.class, Integer.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Long.class, int.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, BigDecimal.class, true, 0));
+    }
+    
+    public void testBuggy() {
+        assertEquals(1, _MethodUtil.isMoreOrSameSpecificParameterType(String.class, String.class, false, 0));
+        assertEquals(1, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, int.class, false, 0));
+        
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Integer.class, false, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(boolean.class, Boolean.class, false, 0));
+        
+        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, long.class, false, 0));
+        assertEquals(3, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, double.class, false, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Long.class, false, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Double.class, false, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Long.class, false, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Double.class, false, 0));
+
+        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(HashMap.class, Map.class, false, 0));
+        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(String.class, CharSequence.class, false, 0));
+        assertEquals(4, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, Number.class, false, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Number.class, false, 0));
+        
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Map.class, String.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, int.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Boolean.class, boolean.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, boolean.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, Boolean.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, String.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(int.class, BigDecimal.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Long.class, Integer.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(long.class, Integer.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Long.class, int.class, true, 0));
+        assertEquals(0, _MethodUtil.isMoreOrSameSpecificParameterType(Integer.class, BigDecimal.class, true, 0));
+    }
+
+}



[11/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed.

Posted by dd...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/EnumerationModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/EnumerationModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/EnumerationModel.java
deleted file mode 100644
index e90aab9..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/EnumerationModel.java
+++ /dev/null
@@ -1,108 +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.beans;
-
-import java.util.Enumeration;
-import java.util.NoSuchElementException;
-
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-
-/**
- * <p>A class that adds {@link TemplateModelIterator} functionality to the
- * {@link Enumeration} interface implementers. 
- * </p> <p>Using the model as a collection model is NOT thread-safe, as 
- * enumerations are inherently not thread-safe.
- * Further, you can iterate over it only once. Attempts to call the
- * {@link #iterator()} method after it was already driven to the end once will 
- * throw an exception.</p>
- */
-
-public class EnumerationModel
-extends
-    BeanModel
-implements
-    TemplateModelIterator,
-    TemplateCollectionModel {
-    private boolean accessed = false;
-    
-    /**
-     * Creates a new model that wraps the specified enumeration object.
-     * @param enumeration the enumeration object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     */
-    public EnumerationModel(Enumeration enumeration, BeansWrapper wrapper) {
-        super(enumeration, wrapper);
-    }
-
-    /**
-     * This allows the enumeration to be used in a <tt>&lt;#list&gt;</tt> block.
-     * @return "this"
-     */
-    @Override
-    public TemplateModelIterator iterator() throws TemplateModelException {
-        synchronized (this) {
-            if (accessed) {
-                throw new TemplateModelException(
-                    "This collection is stateful and can not be iterated over the" +
-                    " second time.");
-            }
-            accessed = true;
-        }
-        return this;
-    }
-    
-    /**
-     * Calls underlying {@link Enumeration#nextElement()}.
-     */
-    @Override
-    public boolean hasNext() {
-        return ((Enumeration) object).hasMoreElements();
-    }
-
-
-    /**
-     * Calls underlying {@link Enumeration#nextElement()} and wraps the result.
-     */
-    @Override
-    public TemplateModel next()
-    throws TemplateModelException {
-        try {
-            return wrap(((Enumeration) object).nextElement());
-        } catch (NoSuchElementException e) {
-            throw new TemplateModelException(
-                "No more elements in the enumeration.");
-        }
-    }
-
-    /**
-     * Returns {@link Enumeration#hasMoreElements()}. Therefore, an
-     * enumeration that has no more element evaluates to false, and an 
-     * enumeration that has further elements evaluates to true.
-     */
-    public boolean getAsBoolean() {
-        return hasNext();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/HashAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/HashAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/HashAdapter.java
deleted file mode 100644
index 0fcc58f..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/HashAdapter.java
+++ /dev/null
@@ -1,180 +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.beans;
-
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-
-/**
- */
-public class HashAdapter extends AbstractMap implements TemplateModelAdapter {
-    private final BeansWrapper wrapper;
-    private final TemplateHashModel model;
-    private Set entrySet;
-    
-    HashAdapter(TemplateHashModel model, BeansWrapper wrapper) {
-        this.model = model;
-        this.wrapper = wrapper;
-    }
-    
-    @Override
-    public TemplateModel getTemplateModel() {
-        return model;
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        try {
-            return model.isEmpty();
-        } catch (TemplateModelException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-    
-    @Override
-    public Object get(Object key) {
-        try {
-            return wrapper.unwrap(model.get(String.valueOf(key)));
-        } catch (TemplateModelException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-
-    @Override
-    public boolean containsKey(Object key) {
-        // A quick check that doesn't require TemplateHashModelEx 
-        if (get(key) != null) {
-            return true;
-        }
-        return super.containsKey(key);
-    }
-    
-    @Override
-    public Set entrySet() {
-        if (entrySet != null) {
-            return entrySet;
-        }
-        return entrySet = new AbstractSet() {
-            @Override
-            public Iterator iterator() {
-                final TemplateModelIterator i;
-                try {
-                     i = getModelEx().keys().iterator();
-                } catch (TemplateModelException e) {
-                    throw new UndeclaredThrowableException(e);
-                }
-                return new Iterator() {
-                    @Override
-                    public boolean hasNext() {
-                        try {
-                            return i.hasNext();
-                        } catch (TemplateModelException e) {
-                            throw new UndeclaredThrowableException(e);
-                        }
-                    }
-                    
-                    @Override
-                    public Object next() {
-                        final Object key;
-                        try {
-                            key = wrapper.unwrap(i.next());
-                        } catch (TemplateModelException e) {
-                            throw new UndeclaredThrowableException(e);
-                        }
-                        return new Map.Entry() {
-                            @Override
-                            public Object getKey() {
-                                return key;
-                            }
-                            
-                            @Override
-                            public Object getValue() {
-                                return get(key);
-                            }
-                            
-                            @Override
-                            public Object setValue(Object value) {
-                                throw new UnsupportedOperationException();
-                            }
-                            
-                            @Override
-                            public boolean equals(Object o) {
-                                if (!(o instanceof Map.Entry))
-                                    return false;
-                                Map.Entry e = (Map.Entry) o;
-                                Object k1 = getKey();
-                                Object k2 = e.getKey();
-                                if (k1 == k2 || (k1 != null && k1.equals(k2))) {
-                                    Object v1 = getValue();
-                                    Object v2 = e.getValue();
-                                    if (v1 == v2 || (v1 != null && v1.equals(v2))) 
-                                        return true;
-                                }
-                                return false;
-                            }
-                        
-                            @Override
-                            public int hashCode() {
-                                Object value = getValue();
-                                return (key == null ? 0 : key.hashCode()) ^
-                                       (value == null ? 0 : value.hashCode());
-                            }
-                        };
-                    }
-                    
-                    @Override
-                    public void remove() {
-                        throw new UnsupportedOperationException();
-                    }
-                };
-            }
-            
-            @Override
-            public int size() {
-                try {
-                    return getModelEx().size();
-                } catch (TemplateModelException e) {
-                    throw new UndeclaredThrowableException(e);
-                }
-            }
-        };
-    }
-    
-    private TemplateHashModelEx getModelEx() {
-        if (model instanceof TemplateHashModelEx) {
-            return ((TemplateHashModelEx) model);
-        }
-        throw new UnsupportedOperationException(
-                "Operation supported only on TemplateHashModelEx. " + 
-                model.getClass().getName() + " does not implement it though.");
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/InvalidPropertyException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/InvalidPropertyException.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/InvalidPropertyException.java
deleted file mode 100755
index 5187b15..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/InvalidPropertyException.java
+++ /dev/null
@@ -1,34 +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.beans;
-
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- * An exception thrown when there is an attempt to access
- * an invalid bean property when we are in a "strict bean" mode
- */
-
-public class InvalidPropertyException extends TemplateModelException {
-	
-    public InvalidPropertyException(String description) {
-        super(description);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/IteratorModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/IteratorModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/IteratorModel.java
deleted file mode 100644
index 767c15e..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/IteratorModel.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.model.impl.beans;
-
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-
-/**
- * <p>A class that adds {@link TemplateModelIterator} functionality to the
- * {@link Iterator} interface implementers. 
- * </p>
- * <p>It differs from the {@link org.apache.freemarker.core.model.impl.SimpleCollection} in that 
- * it inherits from {@link BeanModel}, and therefore you can call methods on 
- * it directly, even to the effect of calling <tt>iterator.remove()</tt> in 
- * the template.</p> <p>Using the model as a collection model is NOT 
- * thread-safe, as iterators are inherently not thread-safe.
- * Further, you can iterate over it only once. Attempts to call the
- * {@link #iterator()} method after it was already driven to the end once will 
- * throw an exception.</p>
- */
-
-public class IteratorModel
-extends
-    BeanModel
-implements
-    TemplateModelIterator,
-    TemplateCollectionModel {
-    private boolean accessed = false;
-    
-    /**
-     * Creates a new model that wraps the specified iterator object.
-     * @param iterator the iterator object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     */
-    public IteratorModel(Iterator iterator, BeansWrapper wrapper) {
-        super(iterator, wrapper);
-    }
-
-    /**
-     * This allows the iterator to be used in a <tt>&lt;#list&gt;</tt> block.
-     * @return "this"
-     */
-    @Override
-    public TemplateModelIterator iterator() throws TemplateModelException {
-        synchronized (this) {
-            if (accessed) {
-                throw new TemplateModelException(
-                    "This collection is stateful and can not be iterated over the" +
-                    " second time.");
-            }
-            accessed = true;
-        }
-        return this;
-    }
-    
-    /**
-     * Calls underlying {@link Iterator#hasNext()}.
-     */
-    @Override
-    public boolean hasNext() {
-        return ((Iterator) object).hasNext();
-    }
-
-
-    /**
-     * Calls underlying {@link Iterator#next()} and wraps the result.
-     */
-    @Override
-    public TemplateModel next()
-    throws TemplateModelException {
-        try {
-            return wrap(((Iterator) object).next());
-        } catch (NoSuchElementException e) {
-            throw new TemplateModelException(
-                "No more elements in the iterator.", e);
-        }
-    }
-
-    /**
-     * Returns {@link Iterator#hasNext()}. Therefore, an
-     * iterator that has no more element evaluates to false, and an 
-     * iterator that has further elements evaluates to true.
-     */
-    public boolean getAsBoolean() {
-        return hasNext();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/JRebelClassChangeNotifier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/JRebelClassChangeNotifier.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/JRebelClassChangeNotifier.java
deleted file mode 100644
index 956a6c4..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/JRebelClassChangeNotifier.java
+++ /dev/null
@@ -1,58 +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.beans;
-
-import java.lang.ref.WeakReference;
-
-import org.zeroturnaround.javarebel.ClassEventListener;
-import org.zeroturnaround.javarebel.ReloaderFactory;
-
-class JRebelClassChangeNotifier implements ClassChangeNotifier {
-
-    static void testAvailability() {
-        ReloaderFactory.getInstance();
-    }
-
-    @Override
-    public void subscribe(ClassIntrospector classIntrospector) {
-        ReloaderFactory.getInstance().addClassReloadListener(
-                new ClassIntrospectorCacheInvalidator(classIntrospector));
-    }
-
-    private static class ClassIntrospectorCacheInvalidator
-            implements ClassEventListener {
-        private final WeakReference ref;
-
-        ClassIntrospectorCacheInvalidator(ClassIntrospector w) {
-            ref = new WeakReference(w);
-        }
-
-        @Override
-        public void onClassEvent(int eventType, Class pClass) {
-            ClassIntrospector ci = (ClassIntrospector) ref.get();
-            if (ci == null) {
-                ReloaderFactory.getInstance().removeClassReloadListener(this);
-            } else if (eventType == ClassEventListener.EVENT_RELOADED) {
-                ci.remove(pClass);
-            }
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/MapModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/MapModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/MapModel.java
deleted file mode 100644
index 0af8749..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/MapModel.java
+++ /dev/null
@@ -1,120 +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.beans;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- * <p>A special case of {@link BeanModel} that adds implementation
- * for {@link TemplateMethodModelEx} on map objects that is a shortcut for the
- * <tt>Map.get()</tt> method. Note that if the passed argument itself is a
- * reflection-wrapper model, then the map lookup will be performed using the
- * wrapped object as the key. Note that you can call <tt>get()</tt> using the
- * <tt>map.key</tt> syntax inherited from {@link BeanModel} as well, 
- * however in that case the key is always a string.</p>
- * <p>The class itself does not implement the {@link org.apache.freemarker.core.model.TemplateCollectionModel}.
- * You can, however use <tt>map.entrySet()</tt>, <tt>map.keySet()</tt>, or
- * <tt>map.values()</tt> to obtain {@link org.apache.freemarker.core.model.TemplateCollectionModel} instances for 
- * various aspects of the map.</p>
- */
-public class MapModel
-extends
-    StringModel
-implements
-    TemplateMethodModelEx {
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new MapModel((Map) object, (BeansWrapper) wrapper);
-            }
-        };
-
-    /**
-     * Creates a new model that wraps the specified map object.
-     * @param map the map object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     */
-    public MapModel(Map map, BeansWrapper wrapper) {
-        super(map, wrapper);
-    }
-
-    /**
-     * The first argument is used as a key to call the map's <tt>get</tt> method.
-     */
-    @Override
-    public Object exec(List arguments)
-    throws TemplateModelException {
-        Object key = unwrap((TemplateModel) arguments.get(0));
-        return wrap(((Map) object).get(key));
-    }
-
-    /**
-     * Overridden to invoke the generic get method by casting to Map instead of 
-     * through reflection - should yield better performance.
-     */
-    @Override
-    protected TemplateModel invokeGenericGet(Map keyMap, Class clazz, String key)
-    throws TemplateModelException {
-        Map map = (Map) object;
-        Object val = map.get(key);
-        if (val == null) {
-            if (key.length() == 1) {
-                // just check for Character key if this is a single-character string
-                Character charKey = Character.valueOf(key.charAt(0));
-                val = map.get(charKey);
-                if (val == null && !(map.containsKey(key) || map.containsKey(charKey))) {
-                    return UNKNOWN;
-                }
-            } else if (!map.containsKey(key)) {
-                return UNKNOWN;
-            }
-        }
-        return wrap(val);
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return ((Map) object).isEmpty() && super.isEmpty();
-    }
-
-    @Override
-    public int size() {
-        return keySet().size();
-    }
-
-    @Override
-    protected Set keySet() {
-        Set set = super.keySet();
-        set.addAll(((Map) object).keySet());
-        return set;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/MaybeEmptyCallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/MaybeEmptyCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/MaybeEmptyCallableMemberDescriptor.java
deleted file mode 100644
index 03791ab..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/MaybeEmptyCallableMemberDescriptor.java
+++ /dev/null
@@ -1,25 +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.beans;
-
-/**
- * Superclass of the {@link EmptyCallableMemberDescriptor} and {@link CallableMemberDescriptor} "case classes".
- */
-abstract class MaybeEmptyCallableMemberDescriptor { }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/MaybeEmptyMemberAndArguments.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/MaybeEmptyMemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/MaybeEmptyMemberAndArguments.java
deleted file mode 100644
index c278243..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/MaybeEmptyMemberAndArguments.java
+++ /dev/null
@@ -1,22 +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.beans;
-
-abstract class MaybeEmptyMemberAndArguments { }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/MemberAndArguments.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/MemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/MemberAndArguments.java
deleted file mode 100644
index b22a63e..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/MemberAndArguments.java
+++ /dev/null
@@ -1,64 +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.beans;
-
-import java.lang.reflect.InvocationTargetException;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- */
-class MemberAndArguments extends MaybeEmptyMemberAndArguments {
-    
-    private final CallableMemberDescriptor callableMemberDesc;
-    private final Object[] args;
-    
-    /**
-     * @param args The already unwrapped arguments
-     */
-    MemberAndArguments(CallableMemberDescriptor callableMemberDesc, Object[] args) {
-        this.callableMemberDesc = callableMemberDesc;
-        this.args = args;
-    }
-    
-    /**
-     * The already unwrapped arguments.
-     */
-    Object[] getArgs() {
-        return args;
-    }
-    
-    TemplateModel invokeMethod(BeansWrapper bw, Object obj)
-            throws TemplateModelException, InvocationTargetException, IllegalAccessException {
-        return callableMemberDesc.invokeMethod(bw, obj, args);
-    }
-
-    Object invokeConstructor(BeansWrapper bw)
-            throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
-            TemplateModelException {
-        return callableMemberDesc.invokeConstructor(bw, args);
-    }
-    
-    CallableMemberDescriptor getCallableMemberDescriptor() {
-        return callableMemberDesc;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/MethodAppearanceFineTuner.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/MethodAppearanceFineTuner.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/MethodAppearanceFineTuner.java
deleted file mode 100644
index ea72fb2..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/MethodAppearanceFineTuner.java
+++ /dev/null
@@ -1,94 +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.beans;
-
-import java.beans.IndexedPropertyDescriptor;
-import java.beans.PropertyDescriptor;
-
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecision;
-import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecisionInput;
-
-/**
- * Used for customizing how the methods are visible from templates, via
- * {@link BeansWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}.
- * The object that implements this should also implement {@link SingletonCustomizer} whenever possible.
- * 
- * @since 2.3.21
- */
-public interface MethodAppearanceFineTuner {
-
-    /**
-     * Implement this to tweak certain aspects of how methods appear in the
-     * data-model. {@link BeansWrapper} will pass in all Java methods here that
-     * it intends to expose in the data-model as methods (so you can do
-     * <tt>obj.foo()</tt> in the template).
-     * With this method you can do the following tweaks:
-     * <ul>
-     *   <li>Hide a method that would be otherwise shown by calling
-     *     {@link MethodAppearanceDecision#setExposeMethodAs(String)}
-     *     with <tt>null</tt> parameter. Note that you can't un-hide methods
-     *     that are not public or are considered to by unsafe
-     *     (like {@link Object#wait()}) because
-     *     {@link #process} is not called for those.</li>
-     *   <li>Show the method with a different name in the data-model than its
-     *     real name by calling
-     *     {@link MethodAppearanceDecision#setExposeMethodAs(String)}
-     *     with non-<tt>null</tt> parameter.
-     *   <li>Create a fake JavaBean property for this method by calling
-     *     {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}.
-     *     For example, if you have <tt>int size()</tt> in a class, but you
-     *     want it to be accessed from the templates as <tt>obj.size</tt>,
-     *     rather than as <tt>obj.size()</tt>, you can do that with this.
-     *     The default is {@code null}, which means that no fake property is
-     *     created for the method. You need not and shouldn't set this
-     *     to non-<tt>null</tt> for the getter methods of real JavaBean
-     *     properties, as those are automatically shown as properties anyway.
-     *     The property name in the {@link PropertyDescriptor} can be anything,
-     *     but the method (or methods) in it must belong to the class that
-     *     is given as the <tt>clazz</tt> parameter or it must be inherited from
-     *     that class, or else whatever errors can occur later.
-     *     {@link IndexedPropertyDescriptor}-s are supported.
-     *     If a real JavaBean property of the same name exists, it won't be
-     *     replaced by the fake one. Also if a fake property of the same name
-     *     was assigned earlier, it won't be replaced.
-     *   <li>Prevent the method to hide a JavaBean property (fake or real) of
-     *     the same name by calling
-     *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)}
-     *     with <tt>false</tt>. The default is <tt>true</tt>, so if you have
-     *     both a property and a method called "foo", then in the template
-     *     <tt>myObject.foo</tt> will return the method itself instead
-     *     of the property value, which is often undesirable.
-     * </ul>
-     * 
-     * <p>Note that you can expose a Java method both as a method and as a
-     * JavaBean property on the same time, however you have to chose different
-     * names for them to prevent shadowing. 
-     * 
-     * @param in Describes the method about which the decision will have to be made.
-     *  
-     * @param out Stores how the method will be exposed in the
-     *   data-model after {@link #process} returns.
-     *   This is initialized so that it reflects the default
-     *   behavior of {@link BeansWrapper}, so you don't have to do anything with this
-     *   when you don't want to change the default behavior.
-     */
-    void process(MethodAppearanceDecisionInput in, MethodAppearanceDecision out);    
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/MethodSorter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/MethodSorter.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/MethodSorter.java
deleted file mode 100644
index d812910..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/MethodSorter.java
+++ /dev/null
@@ -1,32 +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.beans;
-
-import java.beans.MethodDescriptor;
-
-/**
- * Used for JUnit testing method-order dependence bugs via
- * {@link BeansWrapperBuilder#setMethodSorter(MethodSorter)}.
- */
-interface MethodSorter {
-
-    MethodDescriptor[] sortMethodDescriptors(MethodDescriptor[] methodDescriptors);
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ModelCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ModelCache.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ModelCache.java
deleted file mode 100644
index 4c5e415..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ModelCache.java
+++ /dev/null
@@ -1,143 +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.beans;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.SoftReference;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-
-/**
- * Internally used by various wrapper implementations to implement model
- * caching.
- */
-public abstract class ModelCache {
-    private boolean useCache = false;
-    private Map<Object, ModelReference> modelCache = null;
-    private ReferenceQueue<TemplateModel> refQueue = null;
-    
-    protected ModelCache() {
-    }
-    
-    /**
-     * Sets whether this wrapper caches model instances. Default is false.
-     * When set to true, calling {@link #getInstance(Object)} 
-     * multiple times for the same object will return the same model.
-     */
-    public synchronized void setUseCache(boolean useCache) {
-        this.useCache = useCache;
-        if (useCache) {
-            modelCache = new IdentityHashMap<>();
-            refQueue = new ReferenceQueue<>();
-        } else {
-            modelCache = null;
-            refQueue = null;
-        }
-    }
-
-    /**
-     * @since 2.3.21
-     */
-    public synchronized boolean getUseCache() {
-        return useCache;
-    }
-    
-    public TemplateModel getInstance(Object object) {
-        if (object instanceof TemplateModel) {
-            return (TemplateModel) object;
-        }
-        if (object instanceof TemplateModelAdapter) {
-            return ((TemplateModelAdapter) object).getTemplateModel();
-        }
-        if (useCache && isCacheable(object)) {
-            TemplateModel model = lookup(object);
-            if (model == null) {
-                model = create(object);
-                register(model, object);
-            }
-            return model;
-        } else {
-            return create(object);
-        }
-    }
-    
-    protected abstract TemplateModel create(Object object);
-    protected abstract boolean isCacheable(Object object);
-    
-    public void clearCache() {
-        if (modelCache != null) {
-            synchronized (modelCache) {
-                modelCache.clear();
-            }
-        }
-    }
-
-    private TemplateModel lookup(Object object) {
-        ModelReference ref = null;
-        // NOTE: we're doing minimal synchronizations -- which can lead to
-        // duplicate wrapper creation. However, this has no harmful side-effects and
-        // is a lesser performance hit.
-        synchronized (modelCache) {
-            ref = modelCache.get(object);
-        }
-
-        if (ref != null)
-            return ref.getModel();
-
-        return null;
-    }
-
-    private void register(TemplateModel model, Object object) {
-        synchronized (modelCache) {
-            // Remove cleared references
-            for (; ; ) {
-                ModelReference queuedRef = (ModelReference) refQueue.poll();
-                if (queuedRef == null) {
-                    break;
-                }
-                modelCache.remove(queuedRef.object);
-            }
-            // Register new reference
-            modelCache.put(object, new ModelReference(model, object, refQueue));
-        }
-    }
-
-    /**
-     * A special soft reference that is registered in the modelCache.
-     * When it gets cleared (that is, the model became unreachable)
-     * it will remove itself from the model cache.
-     */
-    private static final class ModelReference extends SoftReference<TemplateModel> {
-        Object object;
-
-        ModelReference(TemplateModel ref, Object object, ReferenceQueue<TemplateModel> refQueue) {
-            super(ref, refQueue);
-            this.object = object;
-        }
-
-        TemplateModel getModel() {
-            return get();
-        }
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ModelFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ModelFactory.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ModelFactory.java
deleted file mode 100644
index a09d31b..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ModelFactory.java
+++ /dev/null
@@ -1,34 +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.beans;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateModel;
-
-/**
- * Interface used to create various wrapper models in the {@link ModelCache}.
- */
-public interface ModelFactory {
-    /**
-     * Create a wrapping model for the specified object that belongs to
-     * the specified wrapper.
-     */
-    TemplateModel create(Object object, ObjectWrapper wrapper);
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/NonPrimitiveArrayBackedReadOnlyList.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/NonPrimitiveArrayBackedReadOnlyList.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/NonPrimitiveArrayBackedReadOnlyList.java
deleted file mode 100644
index f2fc97d..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/NonPrimitiveArrayBackedReadOnlyList.java
+++ /dev/null
@@ -1,42 +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.beans;
-
-import java.util.AbstractList;
-
-class NonPrimitiveArrayBackedReadOnlyList extends AbstractList {
-    
-    private final Object[] array;
-    
-    NonPrimitiveArrayBackedReadOnlyList(Object[] array) {
-        this.array = array;
-    }
-
-    @Override
-    public Object get(int index) {
-        return array[index];
-    }
-
-    @Override
-    public int size() {
-        return array.length;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/NumberModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/NumberModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/NumberModel.java
deleted file mode 100644
index 1d0013e..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/NumberModel.java
+++ /dev/null
@@ -1,60 +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.beans;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-
-/**
- * Wraps arbitrary subclass of {@link java.lang.Number} into a reflective model.
- * Beside acting as a {@link TemplateNumberModel}, you can call all Java methods on
- * these objects as well.
- */
-public class NumberModel
-extends
-    BeanModel
-implements
-    TemplateNumberModel {
-    static final ModelFactory FACTORY =
-        new ModelFactory()
-        {
-            @Override
-            public TemplateModel create(Object object, ObjectWrapper wrapper) {
-                return new NumberModel((Number) object, (BeansWrapper) wrapper);
-            }
-        };
-    /**
-     * Creates a new model that wraps the specified number object.
-     * @param number the number object to wrap into a model.
-     * @param wrapper the {@link BeansWrapper} associated with this model.
-     * Every model has to have an associated {@link BeansWrapper} instance. The
-     * model gains many attributes from its wrapper, including the caching 
-     * behavior, method exposure level, method-over-item shadowing policy etc.
-     */
-    public NumberModel(Number number, BeansWrapper wrapper) {
-        super(number, wrapper);
-    }
-
-    @Override
-    public Number getAsNumber() {
-        return (Number) object;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedFixArgsMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedFixArgsMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedFixArgsMethods.java
deleted file mode 100644
index a1de28b..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedFixArgsMethods.java
+++ /dev/null
@@ -1,99 +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.beans;
-
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- * Stores the non-varargs methods for a {@link OverloadedMethods} object.
- */
-class OverloadedFixArgsMethods extends OverloadedMethodsSubset {
-    
-    OverloadedFixArgsMethods() {
-        super();
-    }
-
-    @Override
-    Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) {
-        return memberDesc.getParamTypes();
-    }
-    
-    @Override
-    void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) {
-        // Do nothing
-    }
-
-    @Override
-    MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, BeansWrapper unwrapper) 
-    throws TemplateModelException {
-        if (tmArgs == null) {
-            // null is treated as empty args
-            tmArgs = Collections.EMPTY_LIST;
-        }
-        final int argCount = tmArgs.size();
-        final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();
-        if (unwrappingHintsByParamCount.length <= argCount) {
-            return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS;
-        }
-        Class[] unwarppingHints = unwrappingHintsByParamCount[argCount];
-        if (unwarppingHints == null) {
-            return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS;
-        }
-        
-        Object[] pojoArgs = new Object[argCount];
-        
-        int[] typeFlags = getTypeFlags(argCount);
-        if (typeFlags == ALL_ZEROS_ARRAY) {
-            typeFlags = null;
-        }
-
-        Iterator it = tmArgs.iterator();
-        for (int i = 0; i < argCount; ++i) {
-            Object pojo = unwrapper.tryUnwrapTo(
-                    (TemplateModel) it.next(),
-                    unwarppingHints[i],
-                    typeFlags != null ? typeFlags[i] : 0);
-            if (pojo == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
-                return EmptyMemberAndArguments.noCompatibleOverload(i + 1);
-            }
-            pojoArgs[i] = pojo;
-        }
-        
-        MaybeEmptyCallableMemberDescriptor maybeEmtpyMemberDesc = getMemberDescriptorForArgs(pojoArgs, false);
-        if (maybeEmtpyMemberDesc instanceof CallableMemberDescriptor) {
-            CallableMemberDescriptor memberDesc = (CallableMemberDescriptor) maybeEmtpyMemberDesc;
-            if (typeFlags != null) {
-                // Note that overloaded method selection has already accounted for overflow errors when the method
-                // was selected. So this forced conversion shouldn't cause such corruption. Except, conversion from
-                // BigDecimal is allowed to overflow for backward-compatibility.
-                forceNumberArgumentsToParameterTypes(pojoArgs, memberDesc.getParamTypes(), typeFlags);
-            }
-            return new MemberAndArguments(memberDesc, pojoArgs);
-        } else {
-            return EmptyMemberAndArguments.from((EmptyCallableMemberDescriptor) maybeEmtpyMemberDesc, pojoArgs);
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethods.java
deleted file mode 100644
index f53596b..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethods.java
+++ /dev/null
@@ -1,271 +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.beans;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.freemarker.core._DelayedConversionToString;
-import org.apache.freemarker.core._ErrorDescriptionBuilder;
-import org.apache.freemarker.core._TemplateModelException;
-import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.util.FTLUtil;
-import org.apache.freemarker.core.util._ClassUtil;
-
-/**
- * Used instead of {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} for overloaded methods and
- * constructors.
- * 
- * <p>After the initialization with the {@link #addMethod(Method)} and {@link #addConstructor(Constructor)} calls are
- * done, the instance must be thread-safe. Before that, it's the responsibility of the caller of those methods to
- * ensure that the object is properly publishing to other threads.
- */
-final class OverloadedMethods {
-
-    private final OverloadedMethodsSubset fixArgMethods;
-    private OverloadedMethodsSubset varargMethods;
-    
-    OverloadedMethods() {
-        fixArgMethods = new OverloadedFixArgsMethods();
-    }
-    
-    void addMethod(Method method) {
-        final Class[] paramTypes = method.getParameterTypes();
-        addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(method, paramTypes));
-    }
-
-    void addConstructor(Constructor constr) {
-        final Class[] paramTypes = constr.getParameterTypes();
-        addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(constr, paramTypes));
-    }
-    
-    private void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) {
-        // Note: "varargs" methods are always callable as oms args, with a sequence (array) as the last parameter.
-        fixArgMethods.addCallableMemberDescriptor(memberDesc);
-        if (memberDesc.isVarargs()) {
-            if (varargMethods == null) {
-                varargMethods = new OverloadedVarArgsMethods();
-            }
-            varargMethods.addCallableMemberDescriptor(memberDesc);
-        }
-    }
-    
-    MemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs, BeansWrapper unwrapper) 
-    throws TemplateModelException {
-        // Try to find a oms args match:
-        MaybeEmptyMemberAndArguments fixArgsRes = fixArgMethods.getMemberAndArguments(tmArgs, unwrapper);
-        if (fixArgsRes instanceof MemberAndArguments) {
-            return (MemberAndArguments) fixArgsRes;
-        }
-
-        // Try to find a varargs match:
-        MaybeEmptyMemberAndArguments varargsRes;
-        if (varargMethods != null) {
-            varargsRes = varargMethods.getMemberAndArguments(tmArgs, unwrapper);
-            if (varargsRes instanceof MemberAndArguments) {
-                return (MemberAndArguments) varargsRes;
-            }
-        } else {
-            varargsRes = null;
-        }
-        
-        _ErrorDescriptionBuilder edb = new _ErrorDescriptionBuilder(
-                toCompositeErrorMessage(
-                        (EmptyMemberAndArguments) fixArgsRes,
-                        (EmptyMemberAndArguments) varargsRes,
-                        tmArgs),
-                "\nThe matching overload was searched among these members:\n",
-                memberListToString());
-        addMarkupBITipAfterNoNoMarchIfApplicable(edb, tmArgs);
-        throw new _TemplateModelException(edb);
-    }
-
-    private Object[] toCompositeErrorMessage(
-            final EmptyMemberAndArguments fixArgsEmptyRes, final EmptyMemberAndArguments varargsEmptyRes,
-            List tmArgs) {
-        final Object[] argsErrorMsg;
-        if (varargsEmptyRes != null) {
-            if (fixArgsEmptyRes == null || fixArgsEmptyRes.isNumberOfArgumentsWrong()) {
-                argsErrorMsg = toErrorMessage(varargsEmptyRes, tmArgs);
-            } else {
-                argsErrorMsg = new Object[] {
-                        "When trying to call the non-varargs overloads:\n",
-                        toErrorMessage(fixArgsEmptyRes, tmArgs),
-                        "\nWhen trying to call the varargs overloads:\n",
-                        toErrorMessage(varargsEmptyRes, null)
-                };
-            }
-        } else {
-            argsErrorMsg = toErrorMessage(fixArgsEmptyRes, tmArgs);
-        }
-        return argsErrorMsg;
-    }
-
-    private Object[] toErrorMessage(EmptyMemberAndArguments res, List/*<TemplateModel>*/ tmArgs) {
-        final Object[] unwrappedArgs = res.getUnwrappedArguments();
-        return new Object[] {
-                res.getErrorDescription(),
-                tmArgs != null
-                        ? new Object[] {
-                                "\nThe FTL type of the argument values were: ", getTMActualParameterTypes(tmArgs), "." }
-                        : "",
-                unwrappedArgs != null
-                        ? new Object[] {
-                                "\nThe Java type of the argument values were: ",
-                                getUnwrappedActualParameterTypes(unwrappedArgs) + "." }
-                        : ""};
-    }
-
-    private _DelayedConversionToString memberListToString() {
-        return new _DelayedConversionToString(null) {
-            
-            @Override
-            protected String doConversion(Object obj) {
-                final Iterator fixArgMethodsIter = fixArgMethods.getMemberDescriptors();
-                final Iterator varargMethodsIter = varargMethods != null ? varargMethods.getMemberDescriptors() : null;
-                
-                boolean hasMethods = fixArgMethodsIter.hasNext() || (varargMethodsIter != null && varargMethodsIter.hasNext()); 
-                if (hasMethods) {
-                    StringBuilder sb = new StringBuilder();
-                    HashSet fixArgMethods = new HashSet();
-                    while (fixArgMethodsIter.hasNext()) {
-                        if (sb.length() != 0) sb.append(",\n");
-                        sb.append("    ");
-                        CallableMemberDescriptor callableMemberDesc = (CallableMemberDescriptor) fixArgMethodsIter.next();
-                        fixArgMethods.add(callableMemberDesc);
-                        sb.append(callableMemberDesc.getDeclaration());
-                    }
-                    if (varargMethodsIter != null) {
-                        while (varargMethodsIter.hasNext()) {
-                            CallableMemberDescriptor callableMemberDesc = (CallableMemberDescriptor) varargMethodsIter.next();
-                            if (!fixArgMethods.contains(callableMemberDesc)) {
-                                if (sb.length() != 0) sb.append(",\n");
-                                sb.append("    ");
-                                sb.append(callableMemberDesc.getDeclaration());
-                            }
-                        }
-                    }
-                    return sb.toString();
-                } else {
-                    return "No members";
-                }
-            }
-            
-        };
-    }
-    
-    /**
-     * Adds tip to the error message if converting a {@link TemplateMarkupOutputModel} argument to {@link String} might
-     * allows finding a matching overload. 
-     */
-    private void addMarkupBITipAfterNoNoMarchIfApplicable(_ErrorDescriptionBuilder edb,
-            List tmArgs) {
-        for (int argIdx = 0; argIdx < tmArgs.size(); argIdx++) {
-            Object tmArg = tmArgs.get(argIdx);
-            if (tmArg instanceof TemplateMarkupOutputModel) {
-                for (Iterator membDescs = fixArgMethods.getMemberDescriptors(); membDescs.hasNext();) {
-                    CallableMemberDescriptor membDesc = (CallableMemberDescriptor) membDescs.next();
-                    Class[] paramTypes = membDesc.getParamTypes();
-                    
-                    Class paramType = null;
-                    if (membDesc.isVarargs() && argIdx >= paramTypes.length - 1) {
-                        paramType = paramTypes[paramTypes.length - 1];
-                        if (paramType.isArray()) {
-                            paramType = paramType.getComponentType();
-                        }
-                    }
-                    if (paramType == null && argIdx < paramTypes.length) {
-                        paramType = paramTypes[argIdx];
-                    }
-                    if (paramType != null) {
-                        if (paramType.isAssignableFrom(String.class) && !paramType.isAssignableFrom(tmArg.getClass())) {
-                            edb.tip(SimpleMethodModel.MARKUP_OUTPUT_TO_STRING_TIP);
-                            return;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private _DelayedConversionToString getTMActualParameterTypes(List arguments) {
-        final String[] argumentTypeDescs = new String[arguments.size()];
-        for (int i = 0; i < arguments.size(); i++) {
-            argumentTypeDescs[i] = FTLUtil.getTypeDescription((TemplateModel) arguments.get(i));
-        }
-        
-        return new DelayedCallSignatureToString(argumentTypeDescs) {
-
-            @Override
-            String argumentToString(Object argType) {
-                return (String) argType;
-            }
-            
-        };
-    }
-    
-    private Object getUnwrappedActualParameterTypes(Object[] unwrappedArgs) {
-        final Class[] argumentTypes = new Class[unwrappedArgs.length];
-        for (int i = 0; i < unwrappedArgs.length; i++) {
-            Object unwrappedArg = unwrappedArgs[i];
-            argumentTypes[i] = unwrappedArg != null ? unwrappedArg.getClass() : null;
-        }
-        
-        return new DelayedCallSignatureToString(argumentTypes) {
-
-            @Override
-            String argumentToString(Object argType) {
-                return argType != null
-                        ? _ClassUtil.getShortClassName((Class) argType)
-                        : _ClassUtil.getShortClassNameOfObject(null);
-            }
-            
-        };
-    }
-    
-    private abstract class DelayedCallSignatureToString extends _DelayedConversionToString {
-
-        public DelayedCallSignatureToString(Object[] argTypeArray) {
-            super(argTypeArray);
-        }
-
-        @Override
-        protected String doConversion(Object obj) {
-            Object[] argTypes = (Object[]) obj;
-            
-            StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < argTypes.length; i++) {
-                if (i != 0) sb.append(", ");
-                sb.append(argumentToString(argTypes[i]));
-            }
-            
-            return sb.toString();
-        }
-        
-        abstract String argumentToString(Object argType);
-        
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethodsModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethodsModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethodsModel.java
deleted file mode 100644
index 9951c3e..0000000
--- a/src/main/java/org/apache/freemarker/core/model/impl/beans/OverloadedMethodsModel.java
+++ /dev/null
@@ -1,83 +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.beans;
-
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.impl.SimpleNumber;
-
-/**
- * Wraps a set of same-name overloaded methods behind {@link org.apache.freemarker.core.model.TemplateMethodModel} interface,
- * like if it was a single method, chooses among them behind the scenes on call-time based on the argument values.
- */
-public class OverloadedMethodsModel
-implements
-	TemplateMethodModelEx,
-	TemplateSequenceModel {
-    private final Object object;
-    private final OverloadedMethods overloadedMethods;
-    private final BeansWrapper wrapper;
-    
-    OverloadedMethodsModel(Object object, OverloadedMethods overloadedMethods, BeansWrapper wrapper) {
-        this.object = object;
-        this.overloadedMethods = overloadedMethods;
-        this.wrapper = wrapper;
-    }
-
-    /**
-     * Invokes the method, passing it the arguments from the list. The actual
-     * method to call from several overloaded methods will be chosen based
-     * on the classes of the arguments.
-     * @throws TemplateModelException if the method cannot be chosen
-     * unambiguously.
-     */
-    @Override
-    public Object exec(List arguments)
-    throws TemplateModelException {
-        MemberAndArguments maa = overloadedMethods.getMemberAndArguments(arguments, wrapper);
-        try {
-            return maa.invokeMethod(wrapper, object);
-        } catch (Exception e) {
-            if (e instanceof TemplateModelException) throw (TemplateModelException) e;
-            
-            throw _MethodUtil.newInvocationTemplateModelException(
-                    object,
-                    maa.getCallableMemberDescriptor(),
-                    e);
-        }
-    }
-
-    @Override
-    public TemplateModel get(int index) throws TemplateModelException {
-        return (TemplateModel) exec(Collections.singletonList(
-                new SimpleNumber(Integer.valueOf(index))));
-    }
-
-    @Override
-    public int size() throws TemplateModelException {
-        throw new TemplateModelException("?size is unsupported for " + getClass().getName());
-    }
-}