freemarker-notifications mailing list archives

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

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

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

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

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

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

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java
new file mode 100644
index 0000000..cf9836e
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.AdapterTemplateModel;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.RichObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
+import org.apache.freemarker.core.model.WrapperTemplateModel;
+import org.apache.freemarker.core.model.WrappingTemplateModel;
+
+/**
+ * Model used by {@link DefaultObjectWrapper} when <tt>simpleMapWrapper</tt>
+ * mode is enabled. Provides a simple hash model interface to the
+ * underlying map (does not copy like {@link org.apache.freemarker.core.model.impl.SimpleHash}),
+ * and a method interface to non-string keys.
+ */
+public class SimpleMapModel extends WrappingTemplateModel 
+implements TemplateHashModelEx2, TemplateMethodModelEx, AdapterTemplateModel, 
+WrapperTemplateModel, TemplateModelWithAPISupport {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new SimpleMapModel((Map) object, (DefaultObjectWrapper) wrapper);
+            }
+        };
+
+    private final Map map;
+    
+    public SimpleMapModel(Map map, DefaultObjectWrapper wrapper) {
+        super(wrapper);
+        this.map = map;
+    }
+
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        Object val = map.get(key);
+        if (val == null) {
+            if (key.length() == 1) {
+                // just check for Character key if this is a single-character string
+                Character charKey = Character.valueOf(key.charAt(0));
+                val = map.get(charKey);
+                if (val == null && !(map.containsKey(key) || map.containsKey(charKey))) {
+                    return null;
+                }
+            } else if (!map.containsKey(key)) {
+                return null;
+            }
+        }
+        return wrap(val);
+    }
+    
+    @Override
+    public Object exec(List args) throws TemplateModelException {
+        Object key = ((DefaultObjectWrapper) getObjectWrapper()).unwrap((TemplateModel) args.get(0));
+        Object value = map.get(key);
+        if (value == null && !map.containsKey(key)) {
+            return null;
+        }
+        return wrap(value);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public TemplateCollectionModel keys() {
+        return new CollectionAndSequence(new SimpleSequence(map.keySet(), getObjectWrapper()));
+    }
+
+    @Override
+    public TemplateCollectionModel values() {
+        return new CollectionAndSequence(new SimpleSequence(map.values(), getObjectWrapper()));
+    }
+    
+    @Override
+    public KeyValuePairIterator keyValuePairIterator() {
+        return new MapKeyValuePairIterator(map, getObjectWrapper());
+    }
+
+    @Override
+    public Object getAdaptedObject(Class hint) {
+        return map;
+    }
+    
+    @Override
+    public Object getWrappedObject() {
+        return map;
+    }
+
+    @Override
+    public TemplateModel getAPI() throws TemplateModelException {
+        return ((RichObjectWrapper) getObjectWrapper()).wrapAsAPI(map);
+    }
+}
\ No newline at end of file

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

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

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

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java b/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java
new file mode 100644
index 0000000..70f50f6
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+
+/**
+ * Marker interface useful when used together with {@link MethodAppearanceFineTuner} and such customizer objects, to
+ * indicate that it <b>doesn't contain reference to the {@link ObjectWrapper}</b> (so beware with non-static inner
+ * classes) and can be and should be used in call introspection cache keys. This also implies that you won't
+ * create many instances of the class, rather just reuse the same (or same few) instances over and over. Furthermore,
+ * the instances must be thread-safe. The typical pattern in which this instance should be used is like this:
+ * 
+ * <pre>static class MyMethodAppearanceFineTuner implements MethodAppearanceFineTuner, SingletonCustomizer {
+ *      
+ *    // This is the singleton:
+ *    static final MyMethodAppearanceFineTuner INSTANCE = new MyMethodAppearanceFineTuner();
+ *     
+ *    // Private, so it can't be constructed from outside this class.
+ *    private MyMethodAppearanceFineTuner() { }
+ *
+ *    &#64;Override
+ *    public void fineTuneMethodAppearance(...) {
+ *       // Do something here, only using the parameters and maybe some other singletons. 
+ *       ...
+ *    }
+ *     
+ * }</pre>
+ *
+ * @since 2.3.21
+ */
+public interface SingletonCustomizer {
+
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java b/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java
new file mode 100644
index 0000000..04dd3a5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Utility class for instantiating {@link StaticModel} instances from
+ * templates. If your template's data model contains an instance of
+ * StaticModels (named, say <tt>StaticModels</tt>), then you can
+ * instantiate an arbitrary StaticModel using get syntax (i.e.
+ * <tt>StaticModels["java.lang.System"].currentTimeMillis()</tt>).
+ */
+class StaticModels extends ClassBasedModelFactory {
+    
+    StaticModels(DefaultObjectWrapper wrapper) {
+        super(wrapper);
+    }
+
+    @Override
+    protected TemplateModel createModel(Class clazz) 
+    throws TemplateModelException {
+        return new StaticModel(clazz, getWrapper());
+    }
+}
\ No newline at end of file

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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java b/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java
new file mode 100644
index 0000000..d81ab74
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Flag values and masks for "type flags". "Type flags" is a set of bits that store information about the possible
+ * destination types at a parameter position of overloaded methods. 
+ */
+class TypeFlags {
+
+    /**
+     * Indicates that the unwrapping hint will not be a specific numerical type; it must not be set if there's no
+     * numerical type at the given parameter index.
+     */
+    static final int WIDENED_NUMERICAL_UNWRAPPING_HINT = 1;
+    
+    static final int BYTE = 4;
+    static final int SHORT = 8;
+    static final int INTEGER = 16;
+    static final int LONG = 32;
+    static final int FLOAT = 64;
+    static final int DOUBLE = 128;
+    static final int BIG_INTEGER = 256;
+    static final int BIG_DECIMAL = 512;
+    static final int UNKNOWN_NUMERICAL_TYPE = 1024;
+
+    static final int ACCEPTS_NUMBER = 0x800;
+    static final int ACCEPTS_DATE = 0x1000;
+    static final int ACCEPTS_STRING = 0x2000;
+    static final int ACCEPTS_BOOLEAN = 0x4000;
+    static final int ACCEPTS_MAP = 0x8000;
+    static final int ACCEPTS_LIST = 0x10000;
+    static final int ACCEPTS_SET = 0x20000;
+    static final int ACCEPTS_ARRAY = 0x40000;
+    
+    /**
+     * Indicates the presence of the char or Character type
+     */
+    static final int CHARACTER = 0x80000;
+    
+    static final int ACCEPTS_ANY_OBJECT = ACCEPTS_NUMBER | ACCEPTS_DATE | ACCEPTS_STRING | ACCEPTS_BOOLEAN
+            | ACCEPTS_MAP | ACCEPTS_LIST | ACCEPTS_SET | ACCEPTS_ARRAY;
+    
+    static final int MASK_KNOWN_INTEGERS = BYTE | SHORT | INTEGER | LONG | BIG_INTEGER;
+    static final int MASK_KNOWN_NONINTEGERS = FLOAT | DOUBLE | BIG_DECIMAL;
+    static final int MASK_ALL_KNOWN_NUMERICALS = MASK_KNOWN_INTEGERS | MASK_KNOWN_NONINTEGERS;
+    static final int MASK_ALL_NUMERICALS = MASK_ALL_KNOWN_NUMERICALS | UNKNOWN_NUMERICAL_TYPE;
+    
+    static int classToTypeFlags(Class pClass) {
+        // We start with the most frequent cases  
+        if (pClass == Object.class) {
+            return ACCEPTS_ANY_OBJECT;
+        } else if (pClass == String.class) {
+            return ACCEPTS_STRING;
+        } else if (pClass.isPrimitive()) {
+            if (pClass == Integer.TYPE) return INTEGER | ACCEPTS_NUMBER;
+            else if (pClass == Long.TYPE) return LONG | ACCEPTS_NUMBER;
+            else if (pClass == Double.TYPE) return DOUBLE | ACCEPTS_NUMBER;
+            else if (pClass == Float.TYPE) return FLOAT | ACCEPTS_NUMBER;
+            else if (pClass == Byte.TYPE) return BYTE | ACCEPTS_NUMBER;
+            else if (pClass == Short.TYPE) return SHORT | ACCEPTS_NUMBER;
+            else if (pClass == Character.TYPE) return CHARACTER;
+            else if (pClass == Boolean.TYPE) return ACCEPTS_BOOLEAN;
+            else return 0;
+        } else if (Number.class.isAssignableFrom(pClass)) {
+            if (pClass == Integer.class) return INTEGER | ACCEPTS_NUMBER;
+            else if (pClass == Long.class) return LONG | ACCEPTS_NUMBER;
+            else if (pClass == Double.class) return DOUBLE | ACCEPTS_NUMBER;
+            else if (pClass == Float.class) return FLOAT | ACCEPTS_NUMBER;
+            else if (pClass == Byte.class) return BYTE | ACCEPTS_NUMBER;
+            else if (pClass == Short.class) return SHORT | ACCEPTS_NUMBER;
+            else if (BigDecimal.class.isAssignableFrom(pClass)) return BIG_DECIMAL | ACCEPTS_NUMBER;
+            else if (BigInteger.class.isAssignableFrom(pClass)) return BIG_INTEGER | ACCEPTS_NUMBER;
+            else return UNKNOWN_NUMERICAL_TYPE | ACCEPTS_NUMBER;
+        } else if (pClass.isArray()) {
+            return ACCEPTS_ARRAY;
+        } else {
+            int flags = 0;
+            if (pClass.isAssignableFrom(String.class)) {
+                flags |= ACCEPTS_STRING;
+            }
+            if (pClass.isAssignableFrom(Date.class)) {
+                flags |= ACCEPTS_DATE;
+            }
+            if (pClass.isAssignableFrom(Boolean.class)) {
+                flags |= ACCEPTS_BOOLEAN;
+            }
+            if (pClass.isAssignableFrom(Map.class)) {
+                flags |= ACCEPTS_MAP;
+            }
+            if (pClass.isAssignableFrom(List.class)) {
+                flags |= ACCEPTS_LIST;
+            }
+            if (pClass.isAssignableFrom(Set.class)) {
+                flags |= ACCEPTS_SET;
+            }
+            
+            if (pClass == Character.class) {
+                flags |= CHARACTER;
+            }
+            
+            return flags;
+        } 
+    }
+
+}

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

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


Mime
View raw message