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/05/15 21:23:56 UTC

[40/51] [abbrv] [partial] incubator-freemarker git commit: Restructured project so that freemarker-test-utils depends on freemarker-core (and hence can provide common classes for testing templates, and can use utility classes defined in the core). As a c

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ErrorMessagesTest.java
new file mode 100644
index 0000000..4306a50
--- /dev/null
+++ b/freemarker-core-test/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 DefaultObjectWrapper.Builder(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 DefaultObjectWrapper.Builder(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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
new file mode 100644
index 0000000..92f59fb
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.Builder(Configuration.VERSION_3_0_0)
+                .methodAppearanceFineTuner(GetlessMethodsAsPropertyGettersRule.INSTANCE)
+                .exposeFields(true)
+                .build();
+        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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/GetlessMethodsAsPropertyGettersRule.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/GetlessMethodsAsPropertyGettersRule.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/GetlessMethodsAsPropertyGettersRule.java
new file mode 100644
index 0000000..716f863
--- /dev/null
+++ b/freemarker-core-test/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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/IsApplicableTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/IsApplicableTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/IsApplicableTest.java
new file mode 100644
index 0000000..599e6f2
--- /dev/null
+++ b/freemarker-core-test/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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/IsMoreSpecificParameterTypeTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/IsMoreSpecificParameterTypeTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/IsMoreSpecificParameterTypeTest.java
new file mode 100644
index 0000000..3de3f13
--- /dev/null
+++ b/freemarker-core-test/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));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java
new file mode 100644
index 0000000..96c1adf
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/Java7MembersOnlyDefaultObjectWrapper.java
@@ -0,0 +1,101 @@
+/*
+ * 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");
+    
+    static final 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(new DefaultObjectWrapper.Builder(version).methodAppearanceFineTuner(POST_JAVA_7_FILTER), true);
+    }
+
+    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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ManyObjectsOfDifferentClasses.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ManyObjectsOfDifferentClasses.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ManyObjectsOfDifferentClasses.java
new file mode 100644
index 0000000..fa7ce29
--- /dev/null
+++ b/freemarker-core-test/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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ManyStaticsOfDifferentClasses.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ManyStaticsOfDifferentClasses.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ManyStaticsOfDifferentClasses.java
new file mode 100644
index 0000000..dd73bee
--- /dev/null
+++ b/freemarker-core-test/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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MiscNumericalOperationsTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MiscNumericalOperationsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MiscNumericalOperationsTest.java
new file mode 100644
index 0000000..9e175e5
--- /dev/null
+++ b/freemarker-core-test/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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ModelAPINewInstanceTest.java
new file mode 100644
index 0000000..0b0a614
--- /dev/null
+++ b/freemarker-core-test/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.TestUtil;
+
+import junit.framework.TestCase;
+
+public class ModelAPINewInstanceTest extends TestCase {
+
+    private DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(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/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java
new file mode 100644
index 0000000..2498859
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/ModelCacheTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.Ignore;
+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 DefaultObjectWrapper.Builder(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
+    @Ignore // ModelCache is current removed in FM3
+    public void modelCacheOn() throws Exception {
+        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                .useModelCache(true).build();
+        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 {
+        //
+    }
+    
+}