http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java
new file mode 100644
index 0000000..f5b617d
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.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.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.util.CommonBuilder;
+
+/**
+ * Utility method for caching {@link DefaultObjectWrapper} (and subclasses) 
sigletons per Thread Context Class
+ * Loader.
+ */
+// [FM3] Maybe generalize and publish this functionality
+final class DefaultObjectWrapperTCCLSingletonUtil {
+
+    private DefaultObjectWrapperTCCLSingletonUtil() {
+        // Not meant to be instantiated
+    }
+
+    /**
+     * Contains the common parts of the singleton management for {@link 
DefaultObjectWrapper} and {@link DefaultObjectWrapper}.
+     *
+     * @param dowConstructorInvoker Creates a <em>new</em> read-only object 
wrapper of the desired
+     *     {@link DefaultObjectWrapper} subclass.
+     */
+    static <
+            ObjectWrapperT extends DefaultObjectWrapper,
+            BuilderT extends 
DefaultObjectWrapper.ExtendableBuilder<ObjectWrapperT, BuilderT>>
+    ObjectWrapperT getSingleton(
+            BuilderT builder,
+            Map<ClassLoader, Map<BuilderT, WeakReference<ObjectWrapperT>>> 
instanceCache,
+            ReferenceQueue<ObjectWrapperT> instanceCacheRefQue,
+            _ConstructorInvoker<ObjectWrapperT, BuilderT> 
dowConstructorInvoker) {
+        // DefaultObjectWrapper can't be cached across different Thread 
Context Class Loaders (TCCL), because the result of
+        // a class name (String) to Class mappings depends on it, and the 
staticModels and enumModels need that.
+        // (The ClassIntrospector doesn't have to consider the TCCL, as it 
only works with Class-es, not class
+        // names.)
+        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+
+        Reference<ObjectWrapperT> instanceRef;
+        Map<BuilderT, WeakReference<ObjectWrapperT>> tcclScopedCache;
+        synchronized (instanceCache) {
+            tcclScopedCache = instanceCache.get(tccl);
+            if (tcclScopedCache == null) {
+                tcclScopedCache = new HashMap<>();
+                instanceCache.put(tccl, tcclScopedCache);
+                instanceRef = null;
+            } else {
+                instanceRef = tcclScopedCache.get(builder);
+            }
+        }
+
+        ObjectWrapperT instance = instanceRef != null ? instanceRef.get() : 
null;
+        if (instance != null) {  // cache hit
+            return instance;
+        }
+        // cache miss
+
+        builder = builder.cloneForCacheKey();  // prevent any aliasing issues
+        instance = dowConstructorInvoker.invoke(builder);
+
+        synchronized (instanceCache) {
+            instanceRef = tcclScopedCache.get(builder);
+            ObjectWrapperT concurrentInstance = instanceRef != null ? 
instanceRef.get() : null;
+            if (concurrentInstance == null) {
+                tcclScopedCache.put(builder, new WeakReference<>(instance, 
instanceCacheRefQue));
+            } else {
+                instance = concurrentInstance;
+            }
+        }
+
+        removeClearedReferencesFromCache(instanceCache, instanceCacheRefQue);
+
+        return instance;
+    }
+
+    private static <
+            ObjectWrapperT extends DefaultObjectWrapper, BuilderT extends 
DefaultObjectWrapper.ExtendableBuilder>
+    void removeClearedReferencesFromCache(
+            Map<ClassLoader, Map<BuilderT, WeakReference<ObjectWrapperT>>> 
instanceCache,
+            ReferenceQueue<ObjectWrapperT> instanceCacheRefQue) {
+        Reference<? extends ObjectWrapperT> clearedRef;
+        while ((clearedRef = instanceCacheRefQue.poll()) != null) {
+            synchronized (instanceCache) {
+                findClearedRef: for (Map<BuilderT, 
WeakReference<ObjectWrapperT>> tcclScopedCache : instanceCache.values()) {
+                    for (Iterator<WeakReference<ObjectWrapperT>> it2 = 
tcclScopedCache.values().iterator(); it2.hasNext(); ) {
+                        if (it2.next() == clearedRef) {
+                            it2.remove();
+                            break findClearedRef;
+                        }
+                    }
+                }
+            } // sync
+        } // while poll
+    }
+
+    /**
+     * For internal use only; don't depend on this, there's no backward 
compatibility guarantee at all!
+     * Used when the builder delegates the product creation to something else 
(typically, an instance cache). Calling
+     * {@link CommonBuilder#build()} would be infinite recursion in such cases.
+     */
+    public interface _ConstructorInvoker<ProductT, BuilderT> {
+
+        ProductT invoke(BuilderT builder);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java
new file mode 100644
index 0000000..c87c21f
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+
+/**
+ * As opposed to {@link DefaultIteratorAdapter}, this simpler {@link Iterator} 
adapter is used in situations where the
+ * {@link TemplateModelIterator} won't be assigned to FreeMarker template 
variables, only used internally by
+ * {@code #list} or custom Java code. Because of that, it doesn't have to 
handle the situation where the user tries to
+ * iterate over the same value twice.
+ */
+class DefaultUnassignableIteratorAdapter implements TemplateModelIterator {
+
+    private final Iterator<?> it;
+    private final ObjectWrapper wrapper;
+
+    DefaultUnassignableIteratorAdapter(Iterator<?> it, ObjectWrapper wrapper) {
+        this.it = it;
+        this.wrapper = wrapper;
+    }
+
+    @Override
+    public TemplateModel next() throws TemplateModelException {
+        try {
+            return wrapper.wrap(it.next());
+        } catch (NoSuchElementException e) {
+            throw new TemplateModelException("The collection has no more 
items.", e);
+        }
+    }
+
+    @Override
+    public boolean hasNext() throws TemplateModelException {
+        return it.hasNext();
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java
new file mode 100644
index 0000000..424a7f4
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java
new file mode 100644
index 0000000..2b68125
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EnumModels.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EnumModels.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EnumModels.java
new file mode 100644
index 0000000..e4c96c8
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EnumModels.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.TemplateModel;
+
+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 SimpleHash(map, getWrapper());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
new file mode 100644
index 0000000..add437a
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
@@ -0,0 +1,181 @@
+/*
+ * 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;
+
+/**
+ * Adapts a {@link TemplateHashModel} to a {@link Map}.
+ */
+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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java
new file mode 100644
index 0000000..ff73a05
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java
new file mode 100644
index 0000000..15bc8d7
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java
new file mode 100644
index 0000000..6408117
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java
@@ -0,0 +1,105 @@
+/*
+ * 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.List;
+
+import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Wraps a {@link Method} into the {@link TemplateMethodModelEx} interface. It 
is used by {@link BeanModel} to wrap
+ * non-overloaded methods.
+ */
+public final class JavaMethodModel extends SimpleMethod implements 
TemplateMethodModelEx,
+        _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}.
+     */
+    JavaMethodModel(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 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java
new file mode 100644
index 0000000..85be491
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java
@@ -0,0 +1,77 @@
+/*
+ * 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.Map;
+import java.util.Map.Entry;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePair;
+import 
org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ *  Implementation of {@link KeyValuePairIterator} for a {@link 
TemplateHashModelEx2} that wraps or otherwise uses a
+ *  {@link Map} internally.
+ *
+ *  @since 2.3.25
+ */
+public class MapKeyValuePairIterator implements KeyValuePairIterator {
+
+    private final Iterator<Entry<?, ?>> entrySetIterator;
+    
+    private final ObjectWrapper objectWrapper;
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public <K, V> MapKeyValuePairIterator(Map<?, ?> map, ObjectWrapper 
objectWrapper) {
+        entrySetIterator = ((Map) map).entrySet().iterator();
+        this.objectWrapper = objectWrapper;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return entrySetIterator.hasNext();
+    }
+
+    @Override
+    public KeyValuePair next() {
+        final Entry<?, ?> entry = entrySetIterator.next();
+        return new KeyValuePair() {
+
+            @Override
+            public TemplateModel getKey() throws TemplateModelException {
+                return wrap(entry.getKey());
+            }
+
+            @Override
+            public TemplateModel getValue() throws TemplateModelException {
+                return wrap(entry.getValue());
+            }
+            
+        };
+    }
+    
+    private TemplateModel wrap(Object obj) throws TemplateModelException {
+        return (obj instanceof TemplateModel) ? (TemplateModel) obj : 
objectWrapper.wrap(obj);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java
new file mode 100644
index 0000000..c713ed9
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java
new file mode 100644
index 0000000..d14a343
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java
new file mode 100644
index 0000000..3a37d9d
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
new file mode 100644
index 0000000..c60599d
--- /dev/null
+++ 
b/freemarker-core/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.ExtendableBuilder#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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
new file mode 100644
index 0000000..9218bdf
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java
@@ -0,0 +1,36 @@
+/*
+ * 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.List;
+
+/**
+ * Used for JUnit testing method-order dependence bugs via
+ * {@link DefaultObjectWrapper.Builder#setMethodSorter(MethodSorter)}.
+ */
+interface MethodSorter {
+
+    /**
+     * Sorts the methods in place (that is, by modifying the parameter list).
+     */
+    void sortMethodDescriptors(List<MethodDescriptor> methodDescriptors);
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java
new file mode 100644
index 0000000..1d5bae8
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java
new file mode 100644
index 0000000..bff717d
--- /dev/null
+++ 
b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
new file mode 100644
index 0000000..1ba1a56
--- /dev/null
+++ 
b/freemarker-core/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(JavaMethodModel.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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java
new file mode 100644
index 0000000..9a66a6d
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.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 java.util.List;
+
+import org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Wraps a set of same-name overloaded methods behind {@link 
TemplateMethodModel} interface,
+ * like if it was a single method, chooses among them behind the scenes on 
call-time based on the argument values.
+ */
+class OverloadedMethodsModel implements TemplateMethodModelEx {
+    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);
+        }
+    }
+}

Reply via email to