http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java b/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java new file mode 100644 index 0000000..633687d --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java @@ -0,0 +1,293 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core.model.impl; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +import org.apache.freemarker.core._DelayedConversionToString; +import org.apache.freemarker.core._DelayedJQuote; +import org.apache.freemarker.core._TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.util.BugException; +import org.apache.freemarker.core.util._ClassUtil; + +/** + * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! + */ +public final class _MethodUtil { + + private _MethodUtil() { + // Not meant to be instantiated + } + + /** + * Determines whether the type given as the 1st argument is convertible to the type given as the 2nd argument + * for method call argument conversion. This follows the rules of the Java reflection-based method call, except + * that since we don't have the value here, a boxed class is never seen as convertible to a primitive type. + * + * @return 0 means {@code false}, non-0 means {@code true}. + * That is, 0 is returned less specificity or incomparable specificity, also when if + * then method was aborted because of {@code ifHigherThan}. + * The absolute value of the returned non-0 number symbolizes how more specific it is: + * <ul> + * <li>1: The two classes are identical</li> + * <li>2: The 1st type is primitive, the 2nd type is the corresponding boxing class</li> + * <li>3: Both classes are numerical, and one is convertible into the other with widening conversion. + * E.g., {@code int} is convertible to {@code long} and {#code double}, hence {@code int} is more + * specific. + * This ignores primitive VS boxed mismatches, except that a boxed class is never seen as + * convertible to a primitive class.</li> + * <li>4: One class is {@code instanceof} of the other, but they aren't identical. + * But unlike in Java, primitive numerical types are {@code instanceof} {@link Number} here.</li> + * </ul> + */ + // TODO Seems that we don't use the full functionality of this anymore, so we could simplify this. See usages. + public static int isMoreOrSameSpecificParameterType(final Class specific, final Class generic, boolean bugfixed, + int ifHigherThan) { + if (ifHigherThan >= 4) return 0; + if (generic.isAssignableFrom(specific)) { + // Identity or widening reference conversion: + return generic == specific ? 1 : 4; + } else { + final boolean specificIsPrim = specific.isPrimitive(); + final boolean genericIsPrim = generic.isPrimitive(); + if (specificIsPrim) { + if (genericIsPrim) { + if (ifHigherThan >= 3) return 0; + return isWideningPrimitiveNumberConversion(specific, generic) ? 3 : 0; + } else { // => specificIsPrim && !genericIsPrim + if (bugfixed) { + final Class specificAsBoxed = _ClassUtil.primitiveClassToBoxingClass(specific); + if (specificAsBoxed == generic) { + // A primitive class is more specific than its boxing class, because it can't store null + return 2; + } else if (generic.isAssignableFrom(specificAsBoxed)) { + // Note: This only occurs if `specific` is a primitive numerical, and `generic == Number` + return 4; + } else if (ifHigherThan >= 3) { + return 0; + } else if (Number.class.isAssignableFrom(specificAsBoxed) + && Number.class.isAssignableFrom(generic)) { + return isWideningBoxedNumberConversion(specificAsBoxed, generic) ? 3 : 0; + } else { + return 0; + } + } else { + return 0; + } + } + } else { // => !specificIsPrim + if (ifHigherThan >= 3) return 0; + if (bugfixed && !genericIsPrim + && Number.class.isAssignableFrom(specific) && Number.class.isAssignableFrom(generic)) { + return isWideningBoxedNumberConversion(specific, generic) ? 3 : 0; + } else { + return 0; + } + } + } // of: !generic.isAssignableFrom(specific) + } + + private static boolean isWideningPrimitiveNumberConversion(final Class source, final Class target) { + if (target == Short.TYPE && (source == Byte.TYPE)) { + return true; + } else if (target == Integer.TYPE && + (source == Short.TYPE || source == Byte.TYPE)) { + return true; + } else if (target == Long.TYPE && + (source == Integer.TYPE || source == Short.TYPE || + source == Byte.TYPE)) { + return true; + } else if (target == Float.TYPE && + (source == Long.TYPE || source == Integer.TYPE || + source == Short.TYPE || source == Byte.TYPE)) { + return true; + } else if (target == Double.TYPE && + (source == Float.TYPE || source == Long.TYPE || + source == Integer.TYPE || source == Short.TYPE || + source == Byte.TYPE)) { + return true; + } else { + return false; + } + } + + private static boolean isWideningBoxedNumberConversion(final Class source, final Class target) { + if (target == Short.class && source == Byte.class) { + return true; + } else if (target == Integer.class && + (source == Short.class || source == Byte.class)) { + return true; + } else if (target == Long.class && + (source == Integer.class || source == Short.class || + source == Byte.class)) { + return true; + } else if (target == Float.class && + (source == Long.class || source == Integer.class || + source == Short.class || source == Byte.class)) { + return true; + } else if (target == Double.class && + (source == Float.class || source == Long.class || + source == Integer.class || source == Short.class || + source == Byte.class)) { + return true; + } else { + return false; + } + } + + /** + * Attention, this doesn't handle primitive classes correctly, nor numerical conversions. + */ + public static Set getAssignables(Class c1, Class c2) { + Set s = new HashSet(); + collectAssignables(c1, c2, s); + return s; + } + + private static void collectAssignables(Class c1, Class c2, Set s) { + if (c1.isAssignableFrom(c2)) { + s.add(c1); + } + Class sc = c1.getSuperclass(); + if (sc != null) { + collectAssignables(sc, c2, s); + } + Class[] itf = c1.getInterfaces(); + for (Class anItf : itf) { + collectAssignables(anItf, c2, s); + } + } + + public static Class[] getParameterTypes(Member member) { + if (member instanceof Method) { + return ((Method) member).getParameterTypes(); + } + if (member instanceof Constructor) { + return ((Constructor) member).getParameterTypes(); + } + throw new IllegalArgumentException("\"member\" must be Method or Constructor"); + } + + public static boolean isVarargs(Member member) { + if (member instanceof Method) { + return ((Method) member).isVarArgs(); + } + if (member instanceof Constructor) { + return ((Constructor) member).isVarArgs(); + } + throw new BugException(); + } + + /** + * Returns a more streamlined method or constructor description than {@code Member.toString()} does. + */ + public static String toString(Member member) { + if (!(member instanceof Method || member instanceof Constructor)) { + throw new IllegalArgumentException("\"member\" must be a Method or Constructor"); + } + + StringBuilder sb = new StringBuilder(); + + if ((member.getModifiers() & Modifier.STATIC) != 0) { + sb.append("static "); + } + + String className = _ClassUtil.getShortClassName(member.getDeclaringClass()); + if (className != null) { + sb.append(className); + sb.append('.'); + } + sb.append(member.getName()); + + sb.append('('); + Class[] paramTypes = _MethodUtil.getParameterTypes(member); + for (int i = 0; i < paramTypes.length; i++) { + if (i != 0) sb.append(", "); + String paramTypeDecl = _ClassUtil.getShortClassName(paramTypes[i]); + if (i == paramTypes.length - 1 && paramTypeDecl.endsWith("[]") && _MethodUtil.isVarargs(member)) { + sb.append(paramTypeDecl.substring(0, paramTypeDecl.length() - 2)); + sb.append("..."); + } else { + sb.append(paramTypeDecl); + } + } + sb.append(')'); + + return sb.toString(); + } + + public static Object[] invocationErrorMessageStart(Member member) { + return invocationErrorMessageStart(member, member instanceof Constructor); + } + + private static Object[] invocationErrorMessageStart(Object member, boolean isConstructor) { + return new Object[] { "Java ", isConstructor ? "constructor " : "method ", new _DelayedJQuote(member) }; + } + + public static TemplateModelException newInvocationTemplateModelException(Object object, Member member, Throwable e) { + return newInvocationTemplateModelException( + object, + member, + (member.getModifiers() & Modifier.STATIC) != 0, + member instanceof Constructor, + e); + } + + public static TemplateModelException newInvocationTemplateModelException(Object object, CallableMemberDescriptor callableMemberDescriptor, Throwable e) { + return newInvocationTemplateModelException( + object, + new _DelayedConversionToString(callableMemberDescriptor) { + @Override + protected String doConversion(Object callableMemberDescriptor) { + return ((CallableMemberDescriptor) callableMemberDescriptor).getDeclaration(); + } + }, + callableMemberDescriptor.isStatic(), + callableMemberDescriptor.isConstructor(), + e); + } + + private static TemplateModelException newInvocationTemplateModelException( + Object parentObject, Object member, boolean isStatic, boolean isConstructor, Throwable e) { + while (e instanceof InvocationTargetException) { + Throwable cause = ((InvocationTargetException) e).getTargetException(); + if (cause != null) { + e = cause; + } else { + break; + } + } + + return new _TemplateModelException(e, + invocationErrorMessageStart(member, isConstructor), + " threw an exception", + isStatic || isConstructor ? "" : new Object[] { + " when invoked on ", parentObject.getClass(), " object ", new _DelayedJQuote(parentObject) + }, + "; see cause exception in the Java stack trace."); + } + +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java b/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java new file mode 100644 index 0000000..660173a --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core.model.impl; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.util.BugException; +import org.apache.freemarker.core.util._CollectionUtil; + +/** + * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! + * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can + * access things inside this package that users shouldn't. + */ +public class _ModelAPI { + + private _ModelAPI() { } + + public static Object newInstance(Class<?> pClass, Object[] args, DefaultObjectWrapper ow) + throws NoSuchMethodException, IllegalArgumentException, InstantiationException, + IllegalAccessException, InvocationTargetException, TemplateModelException { + return newInstance(getConstructorDescriptor(pClass, args), args, ow); + } + + /** + * Gets the constructor that matches the types of the arguments the best. So this is more + * than what the Java reflection API provides in that it can handle overloaded constructors. This re-uses the + * overloaded method selection logic of {@link DefaultObjectWrapper}. + */ + private static CallableMemberDescriptor getConstructorDescriptor(Class<?> pClass, Object[] args) + throws NoSuchMethodException { + if (args == null) args = _CollectionUtil.EMPTY_OBJECT_ARRAY; + + final ArgumentTypes argTypes = new ArgumentTypes(args); + final List<ReflectionCallableMemberDescriptor> fixedArgMemberDescs + = new ArrayList<>(); + final List<ReflectionCallableMemberDescriptor> varArgsMemberDescs + = new ArrayList<>(); + for (Constructor<?> constr : pClass.getConstructors()) { + ReflectionCallableMemberDescriptor memberDesc = new ReflectionCallableMemberDescriptor(constr, constr.getParameterTypes()); + if (!_MethodUtil.isVarargs(constr)) { + fixedArgMemberDescs.add(memberDesc); + } else { + varArgsMemberDescs.add(memberDesc); + } + } + + MaybeEmptyCallableMemberDescriptor contrDesc = argTypes.getMostSpecific(fixedArgMemberDescs, false); + if (contrDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) { + contrDesc = argTypes.getMostSpecific(varArgsMemberDescs, true); + } + + if (contrDesc instanceof EmptyCallableMemberDescriptor) { + if (contrDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) { + throw new NoSuchMethodException( + "There's no public " + pClass.getName() + + " constructor with compatible parameter list."); + } else if (contrDesc == EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD) { + throw new NoSuchMethodException( + "There are multiple public " + pClass.getName() + + " constructors that match the compatible parameter list with the same preferability."); + } else { + throw new NoSuchMethodException(); + } + } else { + return (CallableMemberDescriptor) contrDesc; + } + } + + private static Object newInstance(CallableMemberDescriptor constrDesc, Object[] args, DefaultObjectWrapper ow) + throws InstantiationException, IllegalAccessException, InvocationTargetException, IllegalArgumentException, + TemplateModelException { + if (args == null) args = _CollectionUtil.EMPTY_OBJECT_ARRAY; + + final Object[] packedArgs; + if (constrDesc.isVarargs()) { + // We have to put all the varargs arguments into a single array argument. + + final Class<?>[] paramTypes = constrDesc.getParamTypes(); + final int fixedArgCnt = paramTypes.length - 1; + + packedArgs = new Object[fixedArgCnt + 1]; + for (int i = 0; i < fixedArgCnt; i++) { + packedArgs[i] = args[i]; + } + + final Class<?> compType = paramTypes[fixedArgCnt].getComponentType(); + final int varArgCnt = args.length - fixedArgCnt; + final Object varArgsArray = Array.newInstance(compType, varArgCnt); + for (int i = 0; i < varArgCnt; i++) { + Array.set(varArgsArray, i, args[fixedArgCnt + i]); + } + packedArgs[fixedArgCnt] = varArgsArray; + } else { + packedArgs = args; + } + + return constrDesc.invokeConstructor(ow, packedArgs); + } + + /** + * Contains the common parts of the singleton management for {@link DefaultObjectWrapper} and {@link DefaultObjectWrapper}. + * + * @param dowSubclassFactory Creates a <em>new</em> read-only object wrapper of the desired + * {@link DefaultObjectWrapper} subclass. + */ + public static <OW extends DefaultObjectWrapper, OWC extends DefaultObjectWrapperConfiguration> OW + getDefaultObjectWrapperSubclassSingleton( + OWC settings, + Map<ClassLoader, Map<OWC, WeakReference<OW>>> instanceCache, + ReferenceQueue<OW> instanceCacheRefQue, + _DefaultObjectWrapperSubclassFactory<OW, OWC> dowSubclassFactory) { + // DefaultObjectWrapper can't be cached across different Thread Context Class Loaders (TCCL), because the result of + // a class name (String) to Class mappings depends on it, and the staticModels and enumModels need that. + // (The ClassIntrospector doesn't have to consider the TCCL, as it only works with Class-es, not class + // names.) + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + + Reference<OW> instanceRef; + Map<OWC, WeakReference<OW>> tcclScopedCache; + synchronized (instanceCache) { + tcclScopedCache = instanceCache.get(tccl); + if (tcclScopedCache == null) { + tcclScopedCache = new HashMap<>(); + instanceCache.put(tccl, tcclScopedCache); + instanceRef = null; + } else { + instanceRef = tcclScopedCache.get(settings); + } + } + + OW instance = instanceRef != null ? instanceRef.get() : null; + if (instance != null) { // cache hit + return instance; + } + // cache miss + + settings = clone(settings); // prevent any aliasing issues + instance = dowSubclassFactory.create(settings); + if (!instance.isWriteProtected()) { + throw new BugException(); + } + + synchronized (instanceCache) { + instanceRef = tcclScopedCache.get(settings); + OW concurrentInstance = instanceRef != null ? instanceRef.get() : null; + if (concurrentInstance == null) { + tcclScopedCache.put(settings, new WeakReference<>(instance, instanceCacheRefQue)); + } else { + instance = concurrentInstance; + } + } + + removeClearedReferencesFromCache(instanceCache, instanceCacheRefQue); + + return instance; + } + + @SuppressWarnings("unchecked") + private static <BWC extends DefaultObjectWrapperConfiguration> BWC clone(BWC settings) { + return (BWC) settings.clone(true); + } + + private static <BW extends DefaultObjectWrapper, BWC extends DefaultObjectWrapperConfiguration> + void removeClearedReferencesFromCache( + Map<ClassLoader, Map<BWC, WeakReference<BW>>> instanceCache, + ReferenceQueue<BW> instanceCacheRefQue) { + Reference<? extends BW> clearedRef; + while ((clearedRef = instanceCacheRefQue.poll()) != null) { + synchronized (instanceCache) { + findClearedRef: for (Map<BWC, WeakReference<BW>> tcclScopedCache : instanceCache.values()) { + for (Iterator<WeakReference<BW>> it2 = tcclScopedCache.values().iterator(); it2.hasNext(); ) { + if (it2.next() == clearedRef) { + it2.remove(); + break findClearedRef; + } + } + } + } // sync + } // while poll + } + + /** + * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! + */ + public interface _DefaultObjectWrapperSubclassFactory<BW extends DefaultObjectWrapper, BWC extends DefaultObjectWrapperConfiguration> { + + /** Creates a new read-only {@link DefaultObjectWrapper}; used for {@link DefaultObjectWrapperBuilder} and such. */ + BW create(BWC sa); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_ModelImplApi.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_ModelImplApi.java b/src/main/java/org/apache/freemarker/core/model/impl/_ModelImplApi.java deleted file mode 100644 index 20dd1b7..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/_ModelImplApi.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.freemarker.core.model.impl; - -/** - * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! - * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can - * access things inside this package that users shouldn't. - */ -public class _ModelImplApi { - - private _ModelImplApi() { - // Not to be instantiated - } - - /** For unit testing only */ - public static void DefaultObjectWrapperFactory_clearInstanceCache() { - DefaultObjectWrapperBuilder.clearInstanceCache(); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java b/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java index 5f52b10..7c2a035 100644 --- a/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java +++ b/src/main/java/org/apache/freemarker/core/model/impl/_StaticObjectWrappers.java @@ -19,8 +19,6 @@ package org.apache.freemarker.core.model.impl; import org.apache.freemarker.core.Configuration; -import org.apache.freemarker.core.model.impl.beans.BeansWrapper; -import org.apache.freemarker.core.model.impl.beans.BeansWrapperBuilder; /** * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! @@ -36,9 +34,6 @@ public final class _StaticObjectWrappers { public static final DefaultObjectWrapper DEFAULT_OBJECT_WRAPPER = new DefaultObjectWrapperBuilder(Configuration.VERSION_3_0_0).build(); - public static final BeansWrapper BEANS_WRAPPER - = new BeansWrapperBuilder(Configuration.VERSION_3_0_0).build(); - public static final SimpleObjectWrapper SIMPLE_OBJECT_WRAPPER = new SimpleObjectWrapper(Configuration.VERSION_3_0_0); { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/APIModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/APIModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/APIModel.java deleted file mode 100644 index 3550fe8..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/APIModel.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -/** - * Exposes the Java API (and properties) of an object. - * - * <p> - * Notes: - * <ul> - * <li>The exposing level is inherited from the {@link BeansWrapper}</li> - * <li>But methods will always shadow properties and fields with identical name, regardless of {@link BeansWrapper} - * settings</li> - * </ul> - * - * @since 2.3.22 - */ -final class APIModel extends BeanModel { - - APIModel(Object object, BeansWrapper wrapper) { - super(object, wrapper, false); - } - - protected boolean isMethodsShadowItems() { - return true; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ArgumentTypes.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ArgumentTypes.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ArgumentTypes.java deleted file mode 100644 index abac620..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ArgumentTypes.java +++ /dev/null @@ -1,647 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.freemarker.core.model.impl.beans; - -import java.lang.reflect.InvocationTargetException; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.util.BugException; -import org.apache.freemarker.core.util._ClassUtil; - -/** - * The argument types of a method call; usable as cache key. - */ -final class ArgumentTypes { - - /** - * Conversion difficulty: Lowest; Java Reflection will do it automatically. - */ - private static final int CONVERSION_DIFFICULTY_REFLECTION = 0; - - /** - * Conversion difficulty: Medium: Java reflection API won't convert it, FreeMarker has to do it. - */ - private static final int CONVERSION_DIFFICULTY_FREEMARKER = 1; - - /** - * Conversion difficulty: Highest; conversion is not possible. - */ - private static final int CONVERSION_DIFFICULTY_IMPOSSIBLE = 2; - - /** - * The types of the arguments; for varags this contains the exploded list (not the array). - */ - private final Class<?>[] types; - - /** - * @param args The actual arguments. A varargs argument should be present exploded, no as an array. - */ - ArgumentTypes(Object[] args) { - int ln = args.length; - Class<?>[] typesTmp = new Class[ln]; - for (int i = 0; i < ln; ++i) { - Object arg = args[i]; - typesTmp[i] = arg == null - ? Null.class - : arg.getClass(); - } - - // `typesTmp` is used so the array is only modified before it's stored in the final `types` field (see JSR-133) - types = typesTmp; - } - - @Override - public int hashCode() { - int hash = 0; - for (Class<?> type : types) { - hash ^= type.hashCode(); - } - return hash; - } - - @Override - public boolean equals(Object o) { - if (o instanceof ArgumentTypes) { - ArgumentTypes cs = (ArgumentTypes) o; - if (cs.types.length != types.length) { - return false; - } - for (int i = 0; i < types.length; ++i) { - if (cs.types[i] != types[i]) { - return false; - } - } - return true; - } - return false; - } - - /** - * @return Possibly {@link EmptyCallableMemberDescriptor#NO_SUCH_METHOD} or - * {@link EmptyCallableMemberDescriptor#AMBIGUOUS_METHOD}. - */ - MaybeEmptyCallableMemberDescriptor getMostSpecific( - List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) { - LinkedList<CallableMemberDescriptor> applicables = getApplicables(memberDescs, varArg); - if (applicables.isEmpty()) { - return EmptyCallableMemberDescriptor.NO_SUCH_METHOD; - } - if (applicables.size() == 1) { - return applicables.getFirst(); - } - - LinkedList<CallableMemberDescriptor> maximals = new LinkedList<>(); - for (CallableMemberDescriptor applicable : applicables) { - boolean lessSpecific = false; - for (Iterator<CallableMemberDescriptor> maximalsIter = maximals.iterator(); - maximalsIter.hasNext(); ) { - CallableMemberDescriptor maximal = maximalsIter.next(); - final int cmpRes = compareParameterListPreferability( - applicable.getParamTypes(), maximal.getParamTypes(), varArg); - if (cmpRes > 0) { - maximalsIter.remove(); - } else if (cmpRes < 0) { - lessSpecific = true; - } - } - if (!lessSpecific) { - maximals.addLast(applicable); - } - } - if (maximals.size() > 1) { - return EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD; - } - return maximals.getFirst(); - } - - /** - * Tells if among the parameter list of two methods, which one fits this argument list better. - * This method assumes that the parameter lists are applicable to this argument lists; if that's not ensured, - * what the result will be is undefined. - * - * <p>The decision is made by comparing the preferability of each parameter types of the same position in a loop. - * At the end, the parameter list with the more preferred parameters will be the preferred one. If both parameter - * lists has the same amount of preferred parameters, the one that has the first (lower index) preferred parameter - * is the preferred one. Otherwise the two parameter list are considered to be equal in terms of preferability. - * - * <p>If there's no numerical conversion involved, the preferability of two parameter types is decided on how - * specific their types are. For example, {@code String} is more specific than {@link Object} (because - * {@code Object.class.isAssignableFrom(String.class)}-s), and so {@code String} is preferred. Primitive - * types are considered to be more specific than the corresponding boxing class (like {@code boolean} is more - * specific than {@code Boolean}, because the former can't store {@code null}). The preferability decision gets - * trickier when there's a possibility of numerical conversion from the actual argument type to the type of some of - * the parameters. If such conversion is only possible for one of the competing parameter types, that parameter - * automatically wins. If it's possible for both, {@link OverloadedNumberUtil#getArgumentConversionPrice} will - * be used to calculate the conversion "price", and the parameter type with lowest price wins. There are also - * a twist with array-to-list and list-to-array conversions; we try to avoid those, so the parameter where such - * conversion isn't needed will always win. - * - * @param paramTypes1 The parameter types of one of the competing methods - * @param paramTypes2 The parameter types of the other competing method - * @param varArg Whether these competing methods are varargs methods. - * @return More than 0 if the first parameter list is preferred, less then 0 if the other is preferred, - * 0 if there's no decision - */ - int compareParameterListPreferability(Class<?>[] paramTypes1, Class<?>[] paramTypes2, boolean varArg) { - final int argTypesLen = types.length; - final int paramTypes1Len = paramTypes1.length; - final int paramTypes2Len = paramTypes2.length; - //assert varArg || paramTypes1Len == paramTypes2Len; - - int paramList1WeakWinCnt = 0; - int paramList2WeakWinCnt = 0; - int paramList1WinCnt = 0; - int paramList2WinCnt = 0; - int paramList1StrongWinCnt = 0; - int paramList2StrongWinCnt = 0; - int paramList1VeryStrongWinCnt = 0; - int paramList2VeryStrongWinCnt = 0; - int firstWinerParamList = 0; - for (int i = 0; i < argTypesLen; i++) { - final Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, i, varArg); - final Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, i, varArg); - - final int winerParam; // 1 => paramType1; -1 => paramType2; 0 => draw - if (paramType1 == paramType2) { - winerParam = 0; - } else { - final Class<?> argType = types[i]; - final boolean argIsNum = Number.class.isAssignableFrom(argType); - - final int numConvPrice1; - if (argIsNum && _ClassUtil.isNumerical(paramType1)) { - final Class<?> nonPrimParamType1 = paramType1.isPrimitive() - ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1; - numConvPrice1 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType1); - } else { - numConvPrice1 = Integer.MAX_VALUE; - } - // numConvPrice1 is Integer.MAX_VALUE if either: - // - argType and paramType1 aren't both numerical - // - FM doesn't know some of the numerical types, or the conversion between them is not allowed - - final int numConvPrice2; - if (argIsNum && _ClassUtil.isNumerical(paramType2)) { - final Class<?> nonPrimParamType2 = paramType2.isPrimitive() - ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2; - numConvPrice2 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType2); - } else { - numConvPrice2 = Integer.MAX_VALUE; - } - - if (numConvPrice1 == Integer.MAX_VALUE) { - if (numConvPrice2 == Integer.MAX_VALUE) { // No numerical conversions anywhere - // List to array conversions (unwrapping sometimes makes a List instead of an array) - if (List.class.isAssignableFrom(argType) - && (paramType1.isArray() || paramType2.isArray())) { - if (paramType1.isArray()) { - if (paramType2.isArray()) { // both paramType1 and paramType2 are arrays - int r = compareParameterListPreferability_cmpTypeSpecificty( - paramType1.getComponentType(), paramType2.getComponentType()); - // Because we don't know if the List items are instances of the component - // type or not, we prefer the safer choice, which is the more generic array: - if (r > 0) { - winerParam = 2; - paramList2StrongWinCnt++; - } else if (r < 0) { - winerParam = 1; - paramList1StrongWinCnt++; - } else { - winerParam = 0; - } - } else { // paramType1 is array, paramType2 isn't - // Avoid List to array conversion if the other way makes any sense: - if (Collection.class.isAssignableFrom(paramType2)) { - winerParam = 2; - paramList2StrongWinCnt++; - } else { - winerParam = 1; - paramList1WeakWinCnt++; - } - } - } else { // paramType2 is array, paramType1 isn't - // Avoid List to array conversion if the other way makes any sense: - if (Collection.class.isAssignableFrom(paramType1)) { - winerParam = 1; - paramList1StrongWinCnt++; - } else { - winerParam = 2; - paramList2WeakWinCnt++; - } - } - } else if (argType.isArray() - && (List.class.isAssignableFrom(paramType1) - || List.class.isAssignableFrom(paramType2))) { - // Array to List conversions (unwrapping sometimes makes an array instead of a List) - if (List.class.isAssignableFrom(paramType1)) { - if (List.class.isAssignableFrom(paramType2)) { - // Both paramType1 and paramType2 extends List - winerParam = 0; - } else { - // Only paramType1 extends List - winerParam = 2; - paramList2VeryStrongWinCnt++; - } - } else { - // Only paramType2 extends List - winerParam = 1; - paramList1VeryStrongWinCnt++; - } - } else { // No list to/from array conversion - final int r = compareParameterListPreferability_cmpTypeSpecificty( - paramType1, paramType2); - if (r > 0) { - winerParam = 1; - if (r > 1) { - paramList1WinCnt++; - } else { - paramList1WeakWinCnt++; - } - } else if (r < 0) { - winerParam = -1; - if (r < -1) { - paramList2WinCnt++; - } else { - paramList2WeakWinCnt++; - } - } else { - winerParam = 0; - } - } - } else { // No num. conv. of param1, num. conv. of param2 - winerParam = -1; - paramList2WinCnt++; - } - } else if (numConvPrice2 == Integer.MAX_VALUE) { // Num. conv. of param1, not of param2 - winerParam = 1; - paramList1WinCnt++; - } else { // Num. conv. of both param1 and param2 - if (numConvPrice1 != numConvPrice2) { - if (numConvPrice1 < numConvPrice2) { - winerParam = 1; - if (numConvPrice1 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE - && numConvPrice2 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) { - paramList1StrongWinCnt++; - } else { - paramList1WinCnt++; - } - } else { - winerParam = -1; - if (numConvPrice2 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE - && numConvPrice1 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) { - paramList2StrongWinCnt++; - } else { - paramList2WinCnt++; - } - } - } else { - winerParam = (paramType1.isPrimitive() ? 1 : 0) - (paramType2.isPrimitive() ? 1 : 0); - if (winerParam == 1) paramList1WeakWinCnt++; - else if (winerParam == -1) paramList2WeakWinCnt++; - } - } - } // when paramType1 != paramType2 - - if (firstWinerParamList == 0 && winerParam != 0) { - firstWinerParamList = winerParam; - } - } // for each parameter types - - if (paramList1VeryStrongWinCnt != paramList2VeryStrongWinCnt) { - return paramList1VeryStrongWinCnt - paramList2VeryStrongWinCnt; - } else if (paramList1StrongWinCnt != paramList2StrongWinCnt) { - return paramList1StrongWinCnt - paramList2StrongWinCnt; - } else if (paramList1WinCnt != paramList2WinCnt) { - return paramList1WinCnt - paramList2WinCnt; - } else if (paramList1WeakWinCnt != paramList2WeakWinCnt) { - return paramList1WeakWinCnt - paramList2WeakWinCnt; - } else if (firstWinerParamList != 0) { // paramList1WinCnt == paramList2WinCnt - return firstWinerParamList; - } else { // still undecided - if (varArg) { - if (paramTypes1Len == paramTypes2Len) { - // If we had a 0-length varargs array in both methods, we also compare the types at the - // index of the varargs parameter, like if we had a single varargs argument. However, this - // time we don't have an argument type, so we can only decide based on type specificity: - if (argTypesLen == paramTypes1Len - 1) { - Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, argTypesLen, true); - Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, argTypesLen, true); - if (_ClassUtil.isNumerical(paramType1) && _ClassUtil.isNumerical(paramType2)) { - int r = OverloadedNumberUtil.compareNumberTypeSpecificity(paramType1, paramType2); - if (r != 0) return r; - // falls through - } - return compareParameterListPreferability_cmpTypeSpecificty(paramType1, paramType2); - } else { - return 0; - } - } else { - // The method with more oms parameters wins: - return paramTypes1Len - paramTypes2Len; - } - } else { - return 0; - } - } - } - - /** - * Trivial comparison of type specificities; unaware of numerical conversions. - * - * @return Less-than-0, 0, or more-than-0 depending on which side is more specific. The absolute value is 1 if - * the difference is only in primitive VS non-primitive, more otherwise. - */ - private int compareParameterListPreferability_cmpTypeSpecificty( - final Class<?> paramType1, final Class<?> paramType2) { - // The more specific (smaller) type wins. - - final Class<?> nonPrimParamType1 = paramType1.isPrimitive() - ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1; - final Class<?> nonPrimParamType2 = paramType2.isPrimitive() - ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2; - - if (nonPrimParamType1 == nonPrimParamType2) { - if (nonPrimParamType1 != paramType1) { - if (nonPrimParamType2 != paramType2) { - return 0; // identical prim. types; shouldn't ever be reached - } else { - return 1; // param1 is prim., param2 is non prim. - } - } else if (nonPrimParamType2 != paramType2) { - return -1; // param1 is non-prim., param2 is prim. - } else { - return 0; // identical non-prim. types - } - } else if (nonPrimParamType2.isAssignableFrom(nonPrimParamType1)) { - return 2; - } else if (nonPrimParamType1.isAssignableFrom(nonPrimParamType2)) { - return -2; - } if (nonPrimParamType1 == Character.class && nonPrimParamType2.isAssignableFrom(String.class)) { - return 2; // A character is a 1 long string in FTL, so we pretend that it's a String subtype. - } if (nonPrimParamType2 == Character.class && nonPrimParamType1.isAssignableFrom(String.class)) { - return -2; - } else { - return 0; // unrelated types - } - } - - private static Class<?> getParamType(Class<?>[] paramTypes, int paramTypesLen, int i, boolean varArg) { - return varArg && i >= paramTypesLen - 1 - ? paramTypes[paramTypesLen - 1].getComponentType() - : paramTypes[i]; - } - - /** - * Returns all methods that are applicable to actual - * parameter types represented by this ArgumentTypes object. - */ - LinkedList<CallableMemberDescriptor> getApplicables( - List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) { - LinkedList<CallableMemberDescriptor> applicables = new LinkedList<>(); - for (ReflectionCallableMemberDescriptor memberDesc : memberDescs) { - int difficulty = isApplicable(memberDesc, varArg); - if (difficulty != CONVERSION_DIFFICULTY_IMPOSSIBLE) { - if (difficulty == CONVERSION_DIFFICULTY_REFLECTION) { - applicables.add(memberDesc); - } else if (difficulty == CONVERSION_DIFFICULTY_FREEMARKER) { - applicables.add(new SpecialConversionCallableMemberDescriptor(memberDesc)); - } else { - throw new BugException(); - } - } - } - return applicables; - } - - /** - * Returns if the supplied method is applicable to actual - * parameter types represented by this ArgumentTypes object, also tells - * how difficult that conversion is. - * - * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants. - */ - private int isApplicable(ReflectionCallableMemberDescriptor memberDesc, boolean varArg) { - final Class<?>[] paramTypes = memberDesc.getParamTypes(); - final int cl = types.length; - final int fl = paramTypes.length - (varArg ? 1 : 0); - if (varArg) { - if (cl < fl) { - return CONVERSION_DIFFICULTY_IMPOSSIBLE; - } - } else { - if (cl != fl) { - return CONVERSION_DIFFICULTY_IMPOSSIBLE; - } - } - - int maxDifficulty = 0; - for (int i = 0; i < fl; ++i) { - int difficulty = isMethodInvocationConvertible(paramTypes[i], types[i]); - if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) { - return CONVERSION_DIFFICULTY_IMPOSSIBLE; - } - if (maxDifficulty < difficulty) { - maxDifficulty = difficulty; - } - } - if (varArg) { - Class<?> varArgParamType = paramTypes[fl].getComponentType(); - for (int i = fl; i < cl; ++i) { - int difficulty = isMethodInvocationConvertible(varArgParamType, types[i]); - if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) { - return CONVERSION_DIFFICULTY_IMPOSSIBLE; - } - if (maxDifficulty < difficulty) { - maxDifficulty = difficulty; - } - } - } - return maxDifficulty; - } - - /** - * Determines whether a type is convertible to another type via - * method invocation conversion, and if so, what kind of conversion is needed. - * It treates the object type counterpart of primitive types as if they were the primitive types - * (that is, a Boolean actual parameter type matches boolean primitive formal type). This behavior - * is because this method is used to determine applicable methods for - * an actual parameter list, and primitive types are represented by - * their object duals in reflective method calls. - * @param formal the parameter type to which the actual - * parameter type should be convertible; possibly a primitive type - * @param actual the argument type; not a primitive type, maybe {@link Null}. - * - * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants. - */ - private int isMethodInvocationConvertible(final Class<?> formal, final Class<?> actual) { - // Check for identity or widening reference conversion - if (formal.isAssignableFrom(actual) && actual != CharacterOrString.class) { - return CONVERSION_DIFFICULTY_REFLECTION; - } else { - final Class<?> formalNP; - if (formal.isPrimitive()) { - if (actual == Null.class) { - return CONVERSION_DIFFICULTY_IMPOSSIBLE; - } - - formalNP = _ClassUtil.primitiveClassToBoxingClass(formal); - if (actual == formalNP) { - // Character and char, etc. - return CONVERSION_DIFFICULTY_REFLECTION; - } - } else { // formal is non-primitive - if (actual == Null.class) { - return CONVERSION_DIFFICULTY_REFLECTION; - } - - formalNP = formal; - } - if (Number.class.isAssignableFrom(actual) && Number.class.isAssignableFrom(formalNP)) { - return OverloadedNumberUtil.getArgumentConversionPrice(actual, formalNP) == Integer.MAX_VALUE - ? CONVERSION_DIFFICULTY_IMPOSSIBLE : CONVERSION_DIFFICULTY_REFLECTION; - } else if (formal.isArray()) { - // BeansWrapper method/constructor calls convert from List to array automatically - return List.class.isAssignableFrom(actual) - ? CONVERSION_DIFFICULTY_FREEMARKER : CONVERSION_DIFFICULTY_IMPOSSIBLE; - } else if (actual.isArray() && formal.isAssignableFrom(List.class)) { - // BeansWrapper method/constructor calls convert from array to List automatically - return CONVERSION_DIFFICULTY_FREEMARKER; - } else if (actual == CharacterOrString.class - && (formal.isAssignableFrom(String.class) - || formal.isAssignableFrom(Character.class) || formal == char.class)) { - return CONVERSION_DIFFICULTY_FREEMARKER; - } else { - return CONVERSION_DIFFICULTY_IMPOSSIBLE; - } - } - } - - /** - * Symbolizes the class of null (it's missing from Java). - */ - private static class Null { - - // Can't be instantiated - private Null() { } - - } - - /** - * Used instead of {@link ReflectionCallableMemberDescriptor} when the method is only applicable - * ({@link #isApplicable}) with conversion that Java reflection won't do. It delegates to a - * {@link ReflectionCallableMemberDescriptor}, but it adds the necessary conversions to the invocation methods. - */ - private static final class SpecialConversionCallableMemberDescriptor extends CallableMemberDescriptor { - - private final ReflectionCallableMemberDescriptor callableMemberDesc; - - SpecialConversionCallableMemberDescriptor(ReflectionCallableMemberDescriptor callableMemberDesc) { - this.callableMemberDesc = callableMemberDesc; - } - - @Override - TemplateModel invokeMethod(BeansWrapper bw, Object obj, Object[] args) throws TemplateModelException, - InvocationTargetException, IllegalAccessException { - convertArgsToReflectionCompatible(bw, args); - return callableMemberDesc.invokeMethod(bw, obj, args); - } - - @Override - Object invokeConstructor(BeansWrapper bw, Object[] args) throws IllegalArgumentException, - InstantiationException, IllegalAccessException, InvocationTargetException, TemplateModelException { - convertArgsToReflectionCompatible(bw, args); - return callableMemberDesc.invokeConstructor(bw, args); - } - - @Override - String getDeclaration() { - return callableMemberDesc.getDeclaration(); - } - - @Override - boolean isConstructor() { - return callableMemberDesc.isConstructor(); - } - - @Override - boolean isStatic() { - return callableMemberDesc.isStatic(); - } - - @Override - boolean isVarargs() { - return callableMemberDesc.isVarargs(); - } - - @Override - Class<?>[] getParamTypes() { - return callableMemberDesc.getParamTypes(); - } - - @Override - String getName() { - return callableMemberDesc.getName(); - } - - private void convertArgsToReflectionCompatible(BeansWrapper bw, Object[] args) throws TemplateModelException { - Class<?>[] paramTypes = callableMemberDesc.getParamTypes(); - int ln = paramTypes.length; - for (int i = 0; i < ln; i++) { - Class<?> paramType = paramTypes[i]; - final Object arg = args[i]; - if (arg == null) continue; - - // Handle conversion between List and array types, in both directions. Java reflection won't do such - // conversion, so we have to. - // Most reflection-incompatible conversions were already addressed by the unwrapping. The reason - // this one isn't is that for overloaded methods the hint of a given parameter position is often vague, - // so we may end up with a List even if some parameter types at that position are arrays (remember, we - // have to chose one unwrapping target type, despite that we have many possible overloaded methods), or - // the other way around (that happens when AdapterTemplateMoldel returns an array). - // Later, the overloaded method selection will assume that a List argument is applicable to an array - // parameter, and that an array argument is applicable to a List parameter, so we end up with this - // situation. - if (paramType.isArray() && arg instanceof List) { - args[i] = bw.listToArray((List<?>) arg, paramType, null); - } - if (arg.getClass().isArray() && paramType.isAssignableFrom(List.class)) { - args[i] = bw.arrayToList(arg); - } - - // Handle the conversion from CharacterOrString to Character or String: - if (arg instanceof CharacterOrString) { - if (paramType == Character.class || paramType == char.class - || (!paramType.isAssignableFrom(String.class) - && paramType.isAssignableFrom(Character.class))) { - args[i] = Character.valueOf(((CharacterOrString) arg).getAsChar()); - } else { - args[i] = ((CharacterOrString) arg).getAsString(); - } - } - } - } - - } - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ArrayModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ArrayModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ArrayModel.java deleted file mode 100644 index 8be341c..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ArrayModel.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.lang.reflect.Array; - -import org.apache.freemarker.core.model.ObjectWrapper; -import org.apache.freemarker.core.model.TemplateCollectionModel; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateModelIterator; -import org.apache.freemarker.core.model.TemplateSequenceModel; - -/** - * <p>A class that will wrap an arbitrary array into {@link TemplateCollectionModel} - * and {@link TemplateSequenceModel} interfaces. It supports element retrieval through the <tt>array[index]</tt> - * syntax and can be iterated as a list. - */ -public class ArrayModel -extends - BeanModel -implements - TemplateCollectionModel, - TemplateSequenceModel { - static final ModelFactory FACTORY = - new ModelFactory() - { - @Override - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return new ArrayModel(object, (BeansWrapper) wrapper); - } - }; - - // Cached length of the array - private final int length; - - /** - * Creates a new model that wraps the specified array object. - * @param array the array object to wrap into a model. - * @param wrapper the {@link BeansWrapper} associated with this model. - * Every model has to have an associated {@link BeansWrapper} instance. The - * model gains many attributes from its wrapper, including the caching - * behavior, method exposure level, method-over-item shadowing policy etc. - * @throws IllegalArgumentException if the passed object is not a Java array. - */ - public ArrayModel(Object array, BeansWrapper wrapper) { - super(array, wrapper); - Class clazz = array.getClass(); - if (!clazz.isArray()) - throw new IllegalArgumentException("Object is not an array, it's " + array.getClass().getName()); - length = Array.getLength(array); - } - - - @Override - public TemplateModelIterator iterator() { - return new Iterator(); - } - - @Override - public TemplateModel get(int index) - throws TemplateModelException { - try { - return wrap(Array.get(object, index)); - } catch (IndexOutOfBoundsException e) { - return null; -// throw new TemplateModelException("Index out of bounds: " + index); - } - } - - private class Iterator - implements - TemplateSequenceModel, - TemplateModelIterator { - private int position = 0; - - @Override - public boolean hasNext() { - return position < length; - } - - @Override - public TemplateModel get(int index) - throws TemplateModelException { - return ArrayModel.this.get(index); - } - - @Override - public TemplateModel next() - throws TemplateModelException { - return position < length ? get(position++) : null; - } - - @Override - public int size() { - return ArrayModel.this.size(); - } - } - - @Override - public int size() { - return length; - } - - @Override - public boolean isEmpty() { - return length == 0; - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeanModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeanModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeanModel.java deleted file mode 100644 index 5c2db2b..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeanModel.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.beans.IndexedPropertyDescriptor; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.freemarker.core._CoreLogs; -import org.apache.freemarker.core._DelayedFTLTypeDescription; -import org.apache.freemarker.core._DelayedJQuote; -import org.apache.freemarker.core._TemplateModelException; -import org.apache.freemarker.core.model.AdapterTemplateModel; -import org.apache.freemarker.core.model.ObjectWrapper; -import org.apache.freemarker.core.model.TemplateCollectionModel; -import org.apache.freemarker.core.model.TemplateHashModelEx; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateModelIterator; -import org.apache.freemarker.core.model.TemplateModelWithAPISupport; -import org.apache.freemarker.core.model.TemplateScalarModel; -import org.apache.freemarker.core.model.WrapperTemplateModel; -import org.apache.freemarker.core.model.impl.CollectionAndSequence; -import org.apache.freemarker.core.model.impl.SimpleScalar; -import org.apache.freemarker.core.model.impl.SimpleSequence; -import org.apache.freemarker.core.util._StringUtil; -import org.slf4j.Logger; - -/** - * A class that will wrap an arbitrary object into {@link org.apache.freemarker.core.model.TemplateHashModel} - * interface allowing calls to arbitrary property getters and invocation of - * accessible methods on the object from a template using the - * <tt>object.foo</tt> to access properties and <tt>object.bar(arg1, arg2)</tt> to - * invoke methods on it. You can also use the <tt>object.foo[index]</tt> syntax to - * access indexed properties. It uses Beans {@link java.beans.Introspector} - * to dynamically discover the properties and methods. - */ - -public class BeanModel -implements - TemplateHashModelEx, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport { - - private static final Logger LOG = _CoreLogs.BEANS_WRAPPER; - - protected final Object object; - protected final BeansWrapper wrapper; - - // We use this to represent an unknown value as opposed to known value of null (JR) - static final TemplateModel UNKNOWN = new SimpleScalar("UNKNOWN"); - - static final ModelFactory FACTORY = - new ModelFactory() - { - @Override - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return new BeanModel(object, (BeansWrapper) wrapper); - } - }; - - // I've tried to use a volatile ConcurrentHashMap field instead of HashMap + synchronized(this), but oddly it was - // a bit slower, at least on Java 8 u66. - private HashMap<Object, TemplateModel> memberCache; - - /** - * Creates a new model that wraps the specified object. Note that there are - * specialized subclasses of this class for wrapping arrays, collections, - * enumeration, iterators, and maps. Note also that the superclass can be - * used to wrap String objects if only scalar functionality is needed. You - * can also choose to delegate the choice over which model class is used for - * wrapping to {@link BeansWrapper#wrap(Object)}. - * @param object the object to wrap into a model. - * @param wrapper the {@link BeansWrapper} associated with this model. - * Every model has to have an associated {@link BeansWrapper} instance. The - * model gains many attributes from its wrapper, including the caching - * behavior, method exposure level, method-over-item shadowing policy etc. - */ - public BeanModel(Object object, BeansWrapper wrapper) { - // [2.4]: All models were introspected here, then the results was discareded, and get() will just do the - // introspection again. So is this necessary? (The inrospectNow parameter was added in 2.3.21 to allow - // lazy-introspecting BeansWrapper.trueModel|falseModel.) - this(object, wrapper, true); - } - - /** @since 2.3.21 */ - BeanModel(Object object, BeansWrapper wrapper, boolean inrospectNow) { - this.object = object; - this.wrapper = wrapper; - if (inrospectNow && object != null) { - // [2.4]: Could this be removed? - wrapper.getClassIntrospector().get(object.getClass()); - } - } - - /** - * Uses Beans introspection to locate a property or method with name - * matching the key name. If a method or property is found, it's wrapped - * into {@link org.apache.freemarker.core.model.TemplateMethodModelEx} (for a method or - * indexed property), or evaluated on-the-fly and the return value wrapped - * into appropriate model (for a simple property) Models for various - * properties and methods are cached on a per-class basis, so the costly - * introspection is performed only once per property or method of a class. - * (Side-note: this also implies that any class whose method has been called - * will be strongly referred to by the framework and will not become - * unloadable until this class has been unloaded first. Normally this is not - * an issue, but can be in a rare scenario where you create many classes on- - * the-fly. Also, as the cache grows with new classes and methods introduced - * to the framework, it may appear as if it were leaking memory. The - * framework does, however detect class reloads (if you happen to be in an - * environment that does this kind of things--servlet containers do it when - * they reload a web application) and flushes the cache. If no method or - * property matching the key is found, the framework will try to invoke - * methods with signature - * <tt>non-void-return-type get(java.lang.String)</tt>, - * then <tt>non-void-return-type get(java.lang.Object)</tt>, or - * alternatively (if the wrapped object is a resource bundle) - * <tt>Object getObject(java.lang.String)</tt>. - * @throws TemplateModelException if there was no property nor method nor - * a generic <tt>get</tt> method to invoke. - */ - @Override - public TemplateModel get(String key) - throws TemplateModelException { - Class<?> clazz = object.getClass(); - Map<Object, Object> classInfo = wrapper.getClassIntrospector().get(clazz); - TemplateModel retval = null; - - try { - if (wrapper.isMethodsShadowItems()) { - Object fd = classInfo.get(key); - if (fd != null) { - retval = invokeThroughDescriptor(fd, classInfo); - } else { - retval = invokeGenericGet(classInfo, clazz, key); - } - } else { - TemplateModel model = invokeGenericGet(classInfo, clazz, key); - final TemplateModel nullModel = wrapper.wrap(null); - if (model != nullModel && model != UNKNOWN) { - return model; - } - Object fd = classInfo.get(key); - if (fd != null) { - retval = invokeThroughDescriptor(fd, classInfo); - if (retval == UNKNOWN && model == nullModel) { - // This is the (somewhat subtle) case where the generic get() returns null - // and we have no bean info, so we respect the fact that - // the generic get() returns null and return null. (JR) - retval = nullModel; - } - } - } - if (retval == UNKNOWN) { - if (wrapper.isStrict()) { - throw new InvalidPropertyException("No such bean property: " + key); - } else { - logNoSuchKey(key, classInfo); - } - retval = wrapper.wrap(null); - } - return retval; - } catch (TemplateModelException e) { - throw e; - } catch (Exception e) { - throw new _TemplateModelException(e, - "An error has occurred when reading existing sub-variable ", new _DelayedJQuote(key), - "; see cause exception! The type of the containing value was: ", - new _DelayedFTLTypeDescription(this) - ); - } - } - - private void logNoSuchKey(String key, Map<?, ?> keyMap) { - if (LOG.isDebugEnabled()) { - LOG.debug("Key " + _StringUtil.jQuoteNoXSS(key) + " was not found on instance of " + - object.getClass().getName() + ". Introspection information for " + - "the class is: " + keyMap); - } - } - - /** - * Whether the model has a plain get(String) or get(Object) method - */ - - protected boolean hasPlainGetMethod() { - return wrapper.getClassIntrospector().get(object.getClass()).get(ClassIntrospector.GENERIC_GET_KEY) != null; - } - - private TemplateModel invokeThroughDescriptor(Object desc, Map<Object, Object> classInfo) - throws IllegalAccessException, InvocationTargetException, TemplateModelException { - // See if this particular instance has a cached implementation for the requested feature descriptor - TemplateModel cachedModel; - synchronized (this) { - cachedModel = memberCache != null ? memberCache.get(desc) : null; - } - - if (cachedModel != null) { - return cachedModel; - } - - TemplateModel resultModel = UNKNOWN; - if (desc instanceof IndexedPropertyDescriptor) { - Method readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod(); - resultModel = cachedModel = - new SimpleMethodModel(object, readMethod, - ClassIntrospector.getArgTypes(classInfo, readMethod), wrapper); - } else if (desc instanceof PropertyDescriptor) { - PropertyDescriptor pd = (PropertyDescriptor) desc; - resultModel = wrapper.invokeMethod(object, pd.getReadMethod(), null); - // cachedModel remains null, as we don't cache these - } else if (desc instanceof Field) { - resultModel = wrapper.wrap(((Field) desc).get(object)); - // cachedModel remains null, as we don't cache these - } else if (desc instanceof Method) { - Method method = (Method) desc; - resultModel = cachedModel = new SimpleMethodModel( - object, method, ClassIntrospector.getArgTypes(classInfo, method), wrapper); - } else if (desc instanceof OverloadedMethods) { - resultModel = cachedModel = new OverloadedMethodsModel( - object, (OverloadedMethods) desc, wrapper); - } - - // If new cachedModel was created, cache it - if (cachedModel != null) { - synchronized (this) { - if (memberCache == null) { - memberCache = new HashMap<>(); - } - memberCache.put(desc, cachedModel); - } - } - return resultModel; - } - - void clearMemberCache() { - synchronized (this) { - memberCache = null; - } - } - - protected TemplateModel invokeGenericGet(Map/*<Object, Object>*/ classInfo, Class<?> clazz, String key) - throws IllegalAccessException, InvocationTargetException, - TemplateModelException { - Method genericGet = (Method) classInfo.get(ClassIntrospector.GENERIC_GET_KEY); - if (genericGet == null) { - return UNKNOWN; - } - - return wrapper.invokeMethod(object, genericGet, new Object[] { key }); - } - - protected TemplateModel wrap(Object obj) - throws TemplateModelException { - return wrapper.getOuterIdentity().wrap(obj); - } - - protected Object unwrap(TemplateModel model) - throws TemplateModelException { - return wrapper.unwrap(model); - } - - /** - * Tells whether the model is considered to be empty. - * It is empty if the wrapped object is a 0 length {@link String}, or an empty {@link Collection} or and empty - * {@link Map}, or an {@link Iterator} that has no more items, or a {@link Boolean#FALSE}, or {@code null}. - */ - @Override - public boolean isEmpty() { - if (object instanceof String) { - return ((String) object).length() == 0; - } - if (object instanceof Collection) { - return ((Collection<?>) object).isEmpty(); - } - if (object instanceof Iterator) { - return !((Iterator<?>) object).hasNext(); - } - if (object instanceof Map) { - return ((Map<?,?>) object).isEmpty(); - } - // [FM3] Why's FALSE empty? - return object == null || Boolean.FALSE.equals(object); - } - - /** - * Returns the same as {@link #getWrappedObject()}; to ensure that, this method will be final starting from 2.4. - * This behavior of {@link BeanModel} is assumed by some FreeMarker code. - */ - @Override - public Object getAdaptedObject(Class<?> hint) { - return object; // return getWrappedObject(); starting from 2.4 - } - - @Override - public Object getWrappedObject() { - return object; - } - - @Override - public int size() { - return wrapper.getClassIntrospector().keyCount(object.getClass()); - } - - @Override - public TemplateCollectionModel keys() { - return new CollectionAndSequence(new SimpleSequence(keySet(), wrapper)); - } - - @Override - public TemplateCollectionModel values() throws TemplateModelException { - List<Object> values = new ArrayList<>(size()); - TemplateModelIterator it = keys().iterator(); - while (it.hasNext()) { - String key = ((TemplateScalarModel) it.next()).getAsString(); - values.add(get(key)); - } - return new CollectionAndSequence(new SimpleSequence(values, wrapper)); - } - - @Override - public String toString() { - return object.toString(); - } - - /** - * Helper method to support TemplateHashModelEx. Returns the Set of - * Strings which are available via the TemplateHashModel - * interface. Subclasses that override <tt>invokeGenericGet</tt> to - * provide additional hash keys should also override this method. - */ - protected Set/*<Object>*/ keySet() { - return wrapper.getClassIntrospector().keySet(object.getClass()); - } - - @Override - public TemplateModel getAPI() throws TemplateModelException { - return wrapper.wrapAsAPI(object); - } - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansModelCache.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansModelCache.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansModelCache.java deleted file mode 100644 index 52db93d..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansModelCache.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.freemarker.core.model.TemplateModel; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -public class BeansModelCache extends ModelCache { - private final Map classToFactory = new ConcurrentHashMap(); - private final Set mappedClassNames = new HashSet(); - - private final BeansWrapper wrapper; - - BeansModelCache(BeansWrapper wrapper) { - this.wrapper = wrapper; - } - - @Override - protected boolean isCacheable(Object object) { - return object.getClass() != Boolean.class; - } - - @Override - @SuppressFBWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER", justification="Locks for factory creation only") - protected TemplateModel create(Object object) { - Class clazz = object.getClass(); - - ModelFactory factory = (ModelFactory) classToFactory.get(clazz); - - if (factory == null) { - // Synchronized so that we won't unnecessarily create the same factory for multiple times in parallel. - synchronized (classToFactory) { - factory = (ModelFactory) classToFactory.get(clazz); - if (factory == null) { - String className = clazz.getName(); - // clear mappings when class reloading is detected - if (!mappedClassNames.add(className)) { - classToFactory.clear(); - mappedClassNames.clear(); - mappedClassNames.add(className); - } - factory = wrapper.getModelFactory(clazz); - classToFactory.put(clazz, factory); - } - } - } - - return factory.create(object, wrapper); - } -}
