You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/05/14 10:53:08 UTC
[25/51] [partial] incubator-freemarker git commit: Migrated from Ant
to Gradle, and modularized the project. This is an incomplete migration;
there are some TODO-s in the build scripts, and release related tasks are
still missing. What works: Building th
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/APIModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/APIModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/APIModel.java
new file mode 100644
index 0000000..7ae5a71
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/APIModel.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+/**
+ * Exposes the Java API (and properties) of an object.
+ *
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The exposion level is inherited from the {@link DefaultObjectWrapper}</li>
+ * <li>But methods will always shadow properties and fields with identical name, regardless of {@link DefaultObjectWrapper}
+ * settings</li>
+ * </ul>
+ *
+ * @since 2.3.22
+ */
+final class APIModel extends BeanModel {
+
+ APIModel(Object object, DefaultObjectWrapper wrapper) {
+ super(object, wrapper, false);
+ }
+
+ protected boolean isMethodsShadowItems() {
+ return true;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
new file mode 100644
index 0000000..3b346e6
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
@@ -0,0 +1,647 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._ClassUtil;
+
+/**
+ * The argument types of a method call; usable as cache key.
+ */
+final class ArgumentTypes {
+
+ /**
+ * Conversion difficulty: Lowest; Java Reflection will do it automatically.
+ */
+ private static final int CONVERSION_DIFFICULTY_REFLECTION = 0;
+
+ /**
+ * Conversion difficulty: Medium: Java reflection API won't convert it, FreeMarker has to do it.
+ */
+ private static final int CONVERSION_DIFFICULTY_FREEMARKER = 1;
+
+ /**
+ * Conversion difficulty: Highest; conversion is not possible.
+ */
+ private static final int CONVERSION_DIFFICULTY_IMPOSSIBLE = 2;
+
+ /**
+ * The types of the arguments; for varags this contains the exploded list (not the array).
+ */
+ private final Class<?>[] types;
+
+ /**
+ * @param args The actual arguments. A varargs argument should be present exploded, no as an array.
+ */
+ ArgumentTypes(Object[] args) {
+ int ln = args.length;
+ Class<?>[] typesTmp = new Class[ln];
+ for (int i = 0; i < ln; ++i) {
+ Object arg = args[i];
+ typesTmp[i] = arg == null
+ ? Null.class
+ : arg.getClass();
+ }
+
+ // `typesTmp` is used so the array is only modified before it's stored in the final `types` field (see JSR-133)
+ types = typesTmp;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (Class<?> type : types) {
+ hash ^= type.hashCode();
+ }
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ArgumentTypes) {
+ ArgumentTypes cs = (ArgumentTypes) o;
+ if (cs.types.length != types.length) {
+ return false;
+ }
+ for (int i = 0; i < types.length; ++i) {
+ if (cs.types[i] != types[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return Possibly {@link EmptyCallableMemberDescriptor#NO_SUCH_METHOD} or
+ * {@link EmptyCallableMemberDescriptor#AMBIGUOUS_METHOD}.
+ */
+ MaybeEmptyCallableMemberDescriptor getMostSpecific(
+ List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) {
+ LinkedList<CallableMemberDescriptor> applicables = getApplicables(memberDescs, varArg);
+ if (applicables.isEmpty()) {
+ return EmptyCallableMemberDescriptor.NO_SUCH_METHOD;
+ }
+ if (applicables.size() == 1) {
+ return applicables.getFirst();
+ }
+
+ LinkedList<CallableMemberDescriptor> maximals = new LinkedList<>();
+ for (CallableMemberDescriptor applicable : applicables) {
+ boolean lessSpecific = false;
+ for (Iterator<CallableMemberDescriptor> maximalsIter = maximals.iterator();
+ maximalsIter.hasNext(); ) {
+ CallableMemberDescriptor maximal = maximalsIter.next();
+ final int cmpRes = compareParameterListPreferability(
+ applicable.getParamTypes(), maximal.getParamTypes(), varArg);
+ if (cmpRes > 0) {
+ maximalsIter.remove();
+ } else if (cmpRes < 0) {
+ lessSpecific = true;
+ }
+ }
+ if (!lessSpecific) {
+ maximals.addLast(applicable);
+ }
+ }
+ if (maximals.size() > 1) {
+ return EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD;
+ }
+ return maximals.getFirst();
+ }
+
+ /**
+ * Tells if among the parameter list of two methods, which one fits this argument list better.
+ * This method assumes that the parameter lists are applicable to this argument lists; if that's not ensured,
+ * what the result will be is undefined.
+ *
+ * <p>The decision is made by comparing the preferability of each parameter types of the same position in a loop.
+ * At the end, the parameter list with the more preferred parameters will be the preferred one. If both parameter
+ * lists has the same amount of preferred parameters, the one that has the first (lower index) preferred parameter
+ * is the preferred one. Otherwise the two parameter list are considered to be equal in terms of preferability.
+ *
+ * <p>If there's no numerical conversion involved, the preferability of two parameter types is decided on how
+ * specific their types are. For example, {@code String} is more specific than {@link Object} (because
+ * {@code Object.class.isAssignableFrom(String.class)}-s), and so {@code String} is preferred. Primitive
+ * types are considered to be more specific than the corresponding boxing class (like {@code boolean} is more
+ * specific than {@code Boolean}, because the former can't store {@code null}). The preferability decision gets
+ * trickier when there's a possibility of numerical conversion from the actual argument type to the type of some of
+ * the parameters. If such conversion is only possible for one of the competing parameter types, that parameter
+ * automatically wins. If it's possible for both, {@link OverloadedNumberUtil#getArgumentConversionPrice} will
+ * be used to calculate the conversion "price", and the parameter type with lowest price wins. There are also
+ * a twist with array-to-list and list-to-array conversions; we try to avoid those, so the parameter where such
+ * conversion isn't needed will always win.
+ *
+ * @param paramTypes1 The parameter types of one of the competing methods
+ * @param paramTypes2 The parameter types of the other competing method
+ * @param varArg Whether these competing methods are varargs methods.
+ * @return More than 0 if the first parameter list is preferred, less then 0 if the other is preferred,
+ * 0 if there's no decision
+ */
+ int compareParameterListPreferability(Class<?>[] paramTypes1, Class<?>[] paramTypes2, boolean varArg) {
+ final int argTypesLen = types.length;
+ final int paramTypes1Len = paramTypes1.length;
+ final int paramTypes2Len = paramTypes2.length;
+ //assert varArg || paramTypes1Len == paramTypes2Len;
+
+ int paramList1WeakWinCnt = 0;
+ int paramList2WeakWinCnt = 0;
+ int paramList1WinCnt = 0;
+ int paramList2WinCnt = 0;
+ int paramList1StrongWinCnt = 0;
+ int paramList2StrongWinCnt = 0;
+ int paramList1VeryStrongWinCnt = 0;
+ int paramList2VeryStrongWinCnt = 0;
+ int firstWinerParamList = 0;
+ for (int i = 0; i < argTypesLen; i++) {
+ final Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, i, varArg);
+ final Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, i, varArg);
+
+ final int winerParam; // 1 => paramType1; -1 => paramType2; 0 => draw
+ if (paramType1 == paramType2) {
+ winerParam = 0;
+ } else {
+ final Class<?> argType = types[i];
+ final boolean argIsNum = Number.class.isAssignableFrom(argType);
+
+ final int numConvPrice1;
+ if (argIsNum && _ClassUtil.isNumerical(paramType1)) {
+ final Class<?> nonPrimParamType1 = paramType1.isPrimitive()
+ ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1;
+ numConvPrice1 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType1);
+ } else {
+ numConvPrice1 = Integer.MAX_VALUE;
+ }
+ // numConvPrice1 is Integer.MAX_VALUE if either:
+ // - argType and paramType1 aren't both numerical
+ // - FM doesn't know some of the numerical types, or the conversion between them is not allowed
+
+ final int numConvPrice2;
+ if (argIsNum && _ClassUtil.isNumerical(paramType2)) {
+ final Class<?> nonPrimParamType2 = paramType2.isPrimitive()
+ ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2;
+ numConvPrice2 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType2);
+ } else {
+ numConvPrice2 = Integer.MAX_VALUE;
+ }
+
+ if (numConvPrice1 == Integer.MAX_VALUE) {
+ if (numConvPrice2 == Integer.MAX_VALUE) { // No numerical conversions anywhere
+ // List to array conversions (unwrapping sometimes makes a List instead of an array)
+ if (List.class.isAssignableFrom(argType)
+ && (paramType1.isArray() || paramType2.isArray())) {
+ if (paramType1.isArray()) {
+ if (paramType2.isArray()) { // both paramType1 and paramType2 are arrays
+ int r = compareParameterListPreferability_cmpTypeSpecificty(
+ paramType1.getComponentType(), paramType2.getComponentType());
+ // Because we don't know if the List items are instances of the component
+ // type or not, we prefer the safer choice, which is the more generic array:
+ if (r > 0) {
+ winerParam = 2;
+ paramList2StrongWinCnt++;
+ } else if (r < 0) {
+ winerParam = 1;
+ paramList1StrongWinCnt++;
+ } else {
+ winerParam = 0;
+ }
+ } else { // paramType1 is array, paramType2 isn't
+ // Avoid List to array conversion if the other way makes any sense:
+ if (Collection.class.isAssignableFrom(paramType2)) {
+ winerParam = 2;
+ paramList2StrongWinCnt++;
+ } else {
+ winerParam = 1;
+ paramList1WeakWinCnt++;
+ }
+ }
+ } else { // paramType2 is array, paramType1 isn't
+ // Avoid List to array conversion if the other way makes any sense:
+ if (Collection.class.isAssignableFrom(paramType1)) {
+ winerParam = 1;
+ paramList1StrongWinCnt++;
+ } else {
+ winerParam = 2;
+ paramList2WeakWinCnt++;
+ }
+ }
+ } else if (argType.isArray()
+ && (List.class.isAssignableFrom(paramType1)
+ || List.class.isAssignableFrom(paramType2))) {
+ // Array to List conversions (unwrapping sometimes makes an array instead of a List)
+ if (List.class.isAssignableFrom(paramType1)) {
+ if (List.class.isAssignableFrom(paramType2)) {
+ // Both paramType1 and paramType2 extends List
+ winerParam = 0;
+ } else {
+ // Only paramType1 extends List
+ winerParam = 2;
+ paramList2VeryStrongWinCnt++;
+ }
+ } else {
+ // Only paramType2 extends List
+ winerParam = 1;
+ paramList1VeryStrongWinCnt++;
+ }
+ } else { // No list to/from array conversion
+ final int r = compareParameterListPreferability_cmpTypeSpecificty(
+ paramType1, paramType2);
+ if (r > 0) {
+ winerParam = 1;
+ if (r > 1) {
+ paramList1WinCnt++;
+ } else {
+ paramList1WeakWinCnt++;
+ }
+ } else if (r < 0) {
+ winerParam = -1;
+ if (r < -1) {
+ paramList2WinCnt++;
+ } else {
+ paramList2WeakWinCnt++;
+ }
+ } else {
+ winerParam = 0;
+ }
+ }
+ } else { // No num. conv. of param1, num. conv. of param2
+ winerParam = -1;
+ paramList2WinCnt++;
+ }
+ } else if (numConvPrice2 == Integer.MAX_VALUE) { // Num. conv. of param1, not of param2
+ winerParam = 1;
+ paramList1WinCnt++;
+ } else { // Num. conv. of both param1 and param2
+ if (numConvPrice1 != numConvPrice2) {
+ if (numConvPrice1 < numConvPrice2) {
+ winerParam = 1;
+ if (numConvPrice1 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE
+ && numConvPrice2 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) {
+ paramList1StrongWinCnt++;
+ } else {
+ paramList1WinCnt++;
+ }
+ } else {
+ winerParam = -1;
+ if (numConvPrice2 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE
+ && numConvPrice1 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) {
+ paramList2StrongWinCnt++;
+ } else {
+ paramList2WinCnt++;
+ }
+ }
+ } else {
+ winerParam = (paramType1.isPrimitive() ? 1 : 0) - (paramType2.isPrimitive() ? 1 : 0);
+ if (winerParam == 1) paramList1WeakWinCnt++;
+ else if (winerParam == -1) paramList2WeakWinCnt++;
+ }
+ }
+ } // when paramType1 != paramType2
+
+ if (firstWinerParamList == 0 && winerParam != 0) {
+ firstWinerParamList = winerParam;
+ }
+ } // for each parameter types
+
+ if (paramList1VeryStrongWinCnt != paramList2VeryStrongWinCnt) {
+ return paramList1VeryStrongWinCnt - paramList2VeryStrongWinCnt;
+ } else if (paramList1StrongWinCnt != paramList2StrongWinCnt) {
+ return paramList1StrongWinCnt - paramList2StrongWinCnt;
+ } else if (paramList1WinCnt != paramList2WinCnt) {
+ return paramList1WinCnt - paramList2WinCnt;
+ } else if (paramList1WeakWinCnt != paramList2WeakWinCnt) {
+ return paramList1WeakWinCnt - paramList2WeakWinCnt;
+ } else if (firstWinerParamList != 0) { // paramList1WinCnt == paramList2WinCnt
+ return firstWinerParamList;
+ } else { // still undecided
+ if (varArg) {
+ if (paramTypes1Len == paramTypes2Len) {
+ // If we had a 0-length varargs array in both methods, we also compare the types at the
+ // index of the varargs parameter, like if we had a single varargs argument. However, this
+ // time we don't have an argument type, so we can only decide based on type specificity:
+ if (argTypesLen == paramTypes1Len - 1) {
+ Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, argTypesLen, true);
+ Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, argTypesLen, true);
+ if (_ClassUtil.isNumerical(paramType1) && _ClassUtil.isNumerical(paramType2)) {
+ int r = OverloadedNumberUtil.compareNumberTypeSpecificity(paramType1, paramType2);
+ if (r != 0) return r;
+ // falls through
+ }
+ return compareParameterListPreferability_cmpTypeSpecificty(paramType1, paramType2);
+ } else {
+ return 0;
+ }
+ } else {
+ // The method with more oms parameters wins:
+ return paramTypes1Len - paramTypes2Len;
+ }
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Trivial comparison of type specificities; unaware of numerical conversions.
+ *
+ * @return Less-than-0, 0, or more-than-0 depending on which side is more specific. The absolute value is 1 if
+ * the difference is only in primitive VS non-primitive, more otherwise.
+ */
+ private int compareParameterListPreferability_cmpTypeSpecificty(
+ final Class<?> paramType1, final Class<?> paramType2) {
+ // The more specific (smaller) type wins.
+
+ final Class<?> nonPrimParamType1 = paramType1.isPrimitive()
+ ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1;
+ final Class<?> nonPrimParamType2 = paramType2.isPrimitive()
+ ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2;
+
+ if (nonPrimParamType1 == nonPrimParamType2) {
+ if (nonPrimParamType1 != paramType1) {
+ if (nonPrimParamType2 != paramType2) {
+ return 0; // identical prim. types; shouldn't ever be reached
+ } else {
+ return 1; // param1 is prim., param2 is non prim.
+ }
+ } else if (nonPrimParamType2 != paramType2) {
+ return -1; // param1 is non-prim., param2 is prim.
+ } else {
+ return 0; // identical non-prim. types
+ }
+ } else if (nonPrimParamType2.isAssignableFrom(nonPrimParamType1)) {
+ return 2;
+ } else if (nonPrimParamType1.isAssignableFrom(nonPrimParamType2)) {
+ return -2;
+ } if (nonPrimParamType1 == Character.class && nonPrimParamType2.isAssignableFrom(String.class)) {
+ return 2; // A character is a 1 long string in FTL, so we pretend that it's a String subtype.
+ } if (nonPrimParamType2 == Character.class && nonPrimParamType1.isAssignableFrom(String.class)) {
+ return -2;
+ } else {
+ return 0; // unrelated types
+ }
+ }
+
+ private static Class<?> getParamType(Class<?>[] paramTypes, int paramTypesLen, int i, boolean varArg) {
+ return varArg && i >= paramTypesLen - 1
+ ? paramTypes[paramTypesLen - 1].getComponentType()
+ : paramTypes[i];
+ }
+
+ /**
+ * Returns all methods that are applicable to actual
+ * parameter types represented by this ArgumentTypes object.
+ */
+ LinkedList<CallableMemberDescriptor> getApplicables(
+ List<ReflectionCallableMemberDescriptor> memberDescs, boolean varArg) {
+ LinkedList<CallableMemberDescriptor> applicables = new LinkedList<>();
+ for (ReflectionCallableMemberDescriptor memberDesc : memberDescs) {
+ int difficulty = isApplicable(memberDesc, varArg);
+ if (difficulty != CONVERSION_DIFFICULTY_IMPOSSIBLE) {
+ if (difficulty == CONVERSION_DIFFICULTY_REFLECTION) {
+ applicables.add(memberDesc);
+ } else if (difficulty == CONVERSION_DIFFICULTY_FREEMARKER) {
+ applicables.add(new SpecialConversionCallableMemberDescriptor(memberDesc));
+ } else {
+ throw new BugException();
+ }
+ }
+ }
+ return applicables;
+ }
+
+ /**
+ * Returns if the supplied method is applicable to actual
+ * parameter types represented by this ArgumentTypes object, also tells
+ * how difficult that conversion is.
+ *
+ * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants.
+ */
+ private int isApplicable(ReflectionCallableMemberDescriptor memberDesc, boolean varArg) {
+ final Class<?>[] paramTypes = memberDesc.getParamTypes();
+ final int cl = types.length;
+ final int fl = paramTypes.length - (varArg ? 1 : 0);
+ if (varArg) {
+ if (cl < fl) {
+ return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+ }
+ } else {
+ if (cl != fl) {
+ return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+ }
+ }
+
+ int maxDifficulty = 0;
+ for (int i = 0; i < fl; ++i) {
+ int difficulty = isMethodInvocationConvertible(paramTypes[i], types[i]);
+ if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) {
+ return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+ }
+ if (maxDifficulty < difficulty) {
+ maxDifficulty = difficulty;
+ }
+ }
+ if (varArg) {
+ Class<?> varArgParamType = paramTypes[fl].getComponentType();
+ for (int i = fl; i < cl; ++i) {
+ int difficulty = isMethodInvocationConvertible(varArgParamType, types[i]);
+ if (difficulty == CONVERSION_DIFFICULTY_IMPOSSIBLE) {
+ return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+ }
+ if (maxDifficulty < difficulty) {
+ maxDifficulty = difficulty;
+ }
+ }
+ }
+ return maxDifficulty;
+ }
+
+ /**
+ * Determines whether a type is convertible to another type via
+ * method invocation conversion, and if so, what kind of conversion is needed.
+ * It treates the object type counterpart of primitive types as if they were the primitive types
+ * (that is, a Boolean actual parameter type matches boolean primitive formal type). This behavior
+ * is because this method is used to determine applicable methods for
+ * an actual parameter list, and primitive types are represented by
+ * their object duals in reflective method calls.
+ * @param formal the parameter type to which the actual
+ * parameter type should be convertible; possibly a primitive type
+ * @param actual the argument type; not a primitive type, maybe {@link Null}.
+ *
+ * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants.
+ */
+ private int isMethodInvocationConvertible(final Class<?> formal, final Class<?> actual) {
+ // Check for identity or widening reference conversion
+ if (formal.isAssignableFrom(actual) && actual != CharacterOrString.class) {
+ return CONVERSION_DIFFICULTY_REFLECTION;
+ } else {
+ final Class<?> formalNP;
+ if (formal.isPrimitive()) {
+ if (actual == Null.class) {
+ return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+ }
+
+ formalNP = _ClassUtil.primitiveClassToBoxingClass(formal);
+ if (actual == formalNP) {
+ // Character and char, etc.
+ return CONVERSION_DIFFICULTY_REFLECTION;
+ }
+ } else { // formal is non-primitive
+ if (actual == Null.class) {
+ return CONVERSION_DIFFICULTY_REFLECTION;
+ }
+
+ formalNP = formal;
+ }
+ if (Number.class.isAssignableFrom(actual) && Number.class.isAssignableFrom(formalNP)) {
+ return OverloadedNumberUtil.getArgumentConversionPrice(actual, formalNP) == Integer.MAX_VALUE
+ ? CONVERSION_DIFFICULTY_IMPOSSIBLE : CONVERSION_DIFFICULTY_REFLECTION;
+ } else if (formal.isArray()) {
+ // DefaultObjectWrapper method/constructor calls convert from List to array automatically
+ return List.class.isAssignableFrom(actual)
+ ? CONVERSION_DIFFICULTY_FREEMARKER : CONVERSION_DIFFICULTY_IMPOSSIBLE;
+ } else if (actual.isArray() && formal.isAssignableFrom(List.class)) {
+ // DefaultObjectWrapper method/constructor calls convert from array to List automatically
+ return CONVERSION_DIFFICULTY_FREEMARKER;
+ } else if (actual == CharacterOrString.class
+ && (formal.isAssignableFrom(String.class)
+ || formal.isAssignableFrom(Character.class) || formal == char.class)) {
+ return CONVERSION_DIFFICULTY_FREEMARKER;
+ } else {
+ return CONVERSION_DIFFICULTY_IMPOSSIBLE;
+ }
+ }
+ }
+
+ /**
+ * Symbolizes the class of null (it's missing from Java).
+ */
+ private static class Null {
+
+ // Can't be instantiated
+ private Null() { }
+
+ }
+
+ /**
+ * Used instead of {@link ReflectionCallableMemberDescriptor} when the method is only applicable
+ * ({@link #isApplicable}) with conversion that Java reflection won't do. It delegates to a
+ * {@link ReflectionCallableMemberDescriptor}, but it adds the necessary conversions to the invocation methods.
+ */
+ private static final class SpecialConversionCallableMemberDescriptor extends CallableMemberDescriptor {
+
+ private final ReflectionCallableMemberDescriptor callableMemberDesc;
+
+ SpecialConversionCallableMemberDescriptor(ReflectionCallableMemberDescriptor callableMemberDesc) {
+ this.callableMemberDesc = callableMemberDesc;
+ }
+
+ @Override
+ TemplateModel invokeMethod(DefaultObjectWrapper ow, Object obj, Object[] args) throws TemplateModelException,
+ InvocationTargetException, IllegalAccessException {
+ convertArgsToReflectionCompatible(ow, args);
+ return callableMemberDesc.invokeMethod(ow, obj, args);
+ }
+
+ @Override
+ Object invokeConstructor(DefaultObjectWrapper ow, Object[] args) throws IllegalArgumentException,
+ InstantiationException, IllegalAccessException, InvocationTargetException, TemplateModelException {
+ convertArgsToReflectionCompatible(ow, args);
+ return callableMemberDesc.invokeConstructor(ow, args);
+ }
+
+ @Override
+ String getDeclaration() {
+ return callableMemberDesc.getDeclaration();
+ }
+
+ @Override
+ boolean isConstructor() {
+ return callableMemberDesc.isConstructor();
+ }
+
+ @Override
+ boolean isStatic() {
+ return callableMemberDesc.isStatic();
+ }
+
+ @Override
+ boolean isVarargs() {
+ return callableMemberDesc.isVarargs();
+ }
+
+ @Override
+ Class<?>[] getParamTypes() {
+ return callableMemberDesc.getParamTypes();
+ }
+
+ @Override
+ String getName() {
+ return callableMemberDesc.getName();
+ }
+
+ private void convertArgsToReflectionCompatible(DefaultObjectWrapper ow, Object[] args) throws TemplateModelException {
+ Class<?>[] paramTypes = callableMemberDesc.getParamTypes();
+ int ln = paramTypes.length;
+ for (int i = 0; i < ln; i++) {
+ Class<?> paramType = paramTypes[i];
+ final Object arg = args[i];
+ if (arg == null) continue;
+
+ // Handle conversion between List and array types, in both directions. Java reflection won't do such
+ // conversion, so we have to.
+ // Most reflection-incompatible conversions were already addressed by the unwrapping. The reason
+ // this one isn't is that for overloaded methods the hint of a given parameter position is often vague,
+ // so we may end up with a List even if some parameter types at that position are arrays (remember, we
+ // have to chose one unwrapping target type, despite that we have many possible overloaded methods), or
+ // the other way around (that happens when AdapterTemplateMoldel returns an array).
+ // Later, the overloaded method selection will assume that a List argument is applicable to an array
+ // parameter, and that an array argument is applicable to a List parameter, so we end up with this
+ // situation.
+ if (paramType.isArray() && arg instanceof List) {
+ args[i] = ow.listToArray((List<?>) arg, paramType, null);
+ }
+ if (arg.getClass().isArray() && paramType.isAssignableFrom(List.class)) {
+ args[i] = ow.arrayToList(arg);
+ }
+
+ // Handle the conversion from CharacterOrString to Character or String:
+ if (arg instanceof CharacterOrString) {
+ if (paramType == Character.class || paramType == char.class
+ || (!paramType.isAssignableFrom(String.class)
+ && paramType.isAssignableFrom(Character.class))) {
+ args[i] = Character.valueOf(((CharacterOrString) arg).getAsChar());
+ } else {
+ args[i] = ((CharacterOrString) arg).getAsString();
+ }
+ }
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanAndStringModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanAndStringModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanAndStringModel.java
new file mode 100644
index 0000000..c154bba
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanAndStringModel.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+/**
+ * Subclass of {@link BeanModel} that exposes the return value of the {@link
+ * java.lang.Object#toString()} method through the {@link TemplateScalarModel}
+ * interface.
+ */
+// [FM3] Treating all beans as FTL strings was certainly a bad idea in FM2.
+public class BeanAndStringModel extends BeanModel implements TemplateScalarModel {
+
+ /**
+ * 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 BeanAndStringModel(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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
new file mode 100644
index 0000000..91fe9dc
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
@@ -0,0 +1,339 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.beans.IndexedPropertyDescriptor;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.freemarker.core._CoreLogs;
+import org.apache.freemarker.core._DelayedFTLTypeDescription;
+import org.apache.freemarker.core._DelayedJQuote;
+import org.apache.freemarker.core._TemplateModelException;
+import org.apache.freemarker.core.model.AdapterTemplateModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.WrapperTemplateModel;
+import org.apache.freemarker.core.util._StringUtil;
+import org.slf4j.Logger;
+
+/**
+ * A class that will wrap an arbitrary object into {@link org.apache.freemarker.core.model.TemplateHashModel}
+ * interface allowing calls to arbitrary property getters and invocation of
+ * accessible methods on the object from a template using the
+ * <tt>object.foo</tt> to access properties and <tt>object.bar(arg1, arg2)</tt> to
+ * invoke methods on it. You can also use the <tt>object.foo[index]</tt> syntax to
+ * access indexed properties. It uses Beans {@link java.beans.Introspector}
+ * to dynamically discover the properties and methods.
+ */
+
+public class BeanModel
+ implements TemplateHashModelEx, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport {
+
+ private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER;
+
+ protected final Object object;
+ protected final DefaultObjectWrapper wrapper;
+
+ // We use this to represent an unknown value as opposed to known value of null (JR)
+ static final TemplateModel UNKNOWN = new SimpleScalar("UNKNOWN");
+
+ // I've tried to use a volatile ConcurrentHashMap field instead of HashMap + synchronized(this), but oddly it was
+ // a bit slower, at least on Java 8 u66.
+ private HashMap<Object, TemplateModel> memberCache;
+
+ /**
+ * Creates a new model that wraps the specified object. Note that there are
+ * specialized subclasses of this class for wrapping arrays, collections,
+ * enumeration, iterators, and maps. Note also that the superclass can be
+ * used to wrap String objects if only scalar functionality is needed. You
+ * can also choose to delegate the choice over which model class is used for
+ * wrapping to {@link DefaultObjectWrapper#wrap(Object)}.
+ * @param object the object to wrap into a model.
+ * @param wrapper the {@link DefaultObjectWrapper} associated with this model.
+ * Every model has to have an associated {@link DefaultObjectWrapper} instance. The
+ * model gains many attributes from its wrapper, including the caching
+ * behavior, method exposure level, method-over-item shadowing policy etc.
+ */
+ public BeanModel(Object object, DefaultObjectWrapper wrapper) {
+ // [2.4]: All models were introspected here, then the results was discareded, and get() will just do the
+ // introspection again. So is this necessary? (The inrospectNow parameter was added in 2.3.21 to allow
+ // lazy-introspecting DefaultObjectWrapper.trueModel|falseModel.)
+ this(object, wrapper, true);
+ }
+
+ /** @since 2.3.21 */
+ BeanModel(Object object, DefaultObjectWrapper wrapper, boolean inrospectNow) {
+ this.object = object;
+ this.wrapper = wrapper;
+ if (inrospectNow && object != null) {
+ // [2.4]: Could this be removed?
+ wrapper.getClassIntrospector().get(object.getClass());
+ }
+ }
+
+ /**
+ * Uses Beans introspection to locate a property or method with name
+ * matching the key name. If a method or property is found, it's wrapped
+ * into {@link org.apache.freemarker.core.model.TemplateMethodModelEx} (for a method or
+ * indexed property), or evaluated on-the-fly and the return value wrapped
+ * into appropriate model (for a simple property) Models for various
+ * properties and methods are cached on a per-class basis, so the costly
+ * introspection is performed only once per property or method of a class.
+ * (Side-note: this also implies that any class whose method has been called
+ * will be strongly referred to by the framework and will not become
+ * unloadable until this class has been unloaded first. Normally this is not
+ * an issue, but can be in a rare scenario where you invoke 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 get(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 {
+ Object fd = classInfo.get(key);
+ if (fd != null) {
+ retval = invokeThroughDescriptor(fd, classInfo);
+ } else {
+ retval = invokeGenericGet(classInfo, clazz, key);
+ }
+ 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 PropertyDescriptor) {
+ PropertyDescriptor pd = (PropertyDescriptor) desc;
+ Method readMethod = pd.getReadMethod();
+ if (readMethod != null) {
+ // Unlike in FreeMarker 2, we prefer the normal read method even if there's an indexed read method.
+ resultModel = wrapper.invokeMethod(object, readMethod, null);
+ // cachedModel remains null, as we don't cache these
+ } else if (desc instanceof IndexedPropertyDescriptor) {
+ // In FreeMarker 2 we have exposed such indexed properties as sequences, but they can't support
+ // the size() method, so we have discontinued that. People has to call the indexed read method like
+ // any other method.
+ resultModel = UNKNOWN;
+ } else {
+ throw new IllegalStateException("PropertyDescriptor.readMethod shouldn't be null");
+ }
+ } 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 JavaMethodModel(
+ 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/CallableMemberDescriptor.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/CallableMemberDescriptor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/CallableMemberDescriptor.java
new file mode 100644
index 0000000..bbaf6bd
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/CallableMemberDescriptor.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+/**
+ * Packs a {@link Method} or {@link Constructor} together with its parameter types. The actual
+ * {@link Method} or {@link Constructor} is not exposed by the API, because in rare cases calling them require
+ * type conversion that the Java reflection API can't do, hence the developer shouldn't be tempted to call them
+ * directly.
+ */
+abstract class CallableMemberDescriptor extends MaybeEmptyCallableMemberDescriptor {
+
+ abstract TemplateModel invokeMethod(DefaultObjectWrapper ow, Object obj, Object[] args)
+ throws TemplateModelException, InvocationTargetException, IllegalAccessException;
+
+ abstract Object invokeConstructor(DefaultObjectWrapper ow, Object[] args)
+ throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
+ TemplateModelException;
+
+ abstract String getDeclaration();
+
+ abstract boolean isConstructor();
+
+ abstract boolean isStatic();
+
+ abstract boolean isVarargs();
+
+ abstract Class[] getParamTypes();
+
+ abstract String getName();
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/CharacterOrString.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/CharacterOrString.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/CharacterOrString.java
new file mode 100644
index 0000000..6026011
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/CharacterOrString.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.model.TemplateScalarModel;
+
+/**
+ * Represents value unwrapped both to {@link Character} and {@link String}. This is needed for unwrapped overloaded
+ * method parameters where both {@link Character} and {@link String} occurs on the same parameter position when the
+ * {@link TemplateScalarModel} to unwrapp contains a {@link String} of length 1.
+ */
+final class CharacterOrString {
+
+ private final String stringValue;
+
+ CharacterOrString(String stringValue) {
+ this.stringValue = stringValue;
+ }
+
+ String getAsString() {
+ return stringValue;
+ }
+
+ char getAsChar() {
+ return stringValue.charAt(0);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
new file mode 100644
index 0000000..3fd3a2d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._ClassUtil;
+
+/**
+ * Base class for hash models keyed by Java class names.
+ */
+abstract class ClassBasedModelFactory implements TemplateHashModel {
+ private final DefaultObjectWrapper wrapper;
+
+ private final Map/*<String,TemplateModel>*/ cache = new ConcurrentHashMap();
+ private final Set classIntrospectionsInProgress = new HashSet();
+
+ protected ClassBasedModelFactory(DefaultObjectWrapper wrapper) {
+ this.wrapper = wrapper;
+ }
+
+ @Override
+ public TemplateModel get(String key) throws TemplateModelException {
+ try {
+ return getInternal(key);
+ } catch (Exception e) {
+ if (e instanceof TemplateModelException) {
+ throw (TemplateModelException) e;
+ } else {
+ throw new TemplateModelException(e);
+ }
+ }
+ }
+
+ private TemplateModel getInternal(String key) throws TemplateModelException, ClassNotFoundException {
+ {
+ TemplateModel model = (TemplateModel) cache.get(key);
+ if (model != null) return model;
+ }
+
+ final ClassIntrospector classIntrospector;
+ int classIntrospectorClearingCounter;
+ final Object sharedLock = wrapper.getSharedIntrospectionLock();
+ synchronized (sharedLock) {
+ TemplateModel model = (TemplateModel) cache.get(key);
+ if (model != null) return model;
+
+ while (model == null
+ && classIntrospectionsInProgress.contains(key)) {
+ // Another thread is already introspecting this class;
+ // waiting for its result.
+ try {
+ sharedLock.wait();
+ model = (TemplateModel) cache.get(key);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(
+ "Class inrospection data lookup aborded: " + e);
+ }
+ }
+ if (model != null) return model;
+
+ // This will be the thread that introspects this class.
+ classIntrospectionsInProgress.add(key);
+
+ // While the classIntrospector should not be changed from another thread, badly written apps can do that,
+ // and it's cheap to get the classIntrospector from inside the lock here:
+ classIntrospector = wrapper.getClassIntrospector();
+ classIntrospectorClearingCounter = classIntrospector.getClearingCounter();
+ }
+ try {
+ final Class clazz = _ClassUtil.forName(key);
+
+ // This is called so that we trigger the
+ // class-reloading detector. If clazz is a reloaded class,
+ // the wrapper will in turn call our clearCache method.
+ // TODO: Why do we check it now and only now?
+ classIntrospector.get(clazz);
+
+ TemplateModel model = createModel(clazz);
+ // Warning: model will be null if the class is not good for the subclass.
+ // For example, EnumModels#createModel returns null if clazz is not an enum.
+
+ if (model != null) {
+ synchronized (sharedLock) {
+ // Save it into the cache, but only if nothing relevant has changed while we were outside the lock:
+ if (classIntrospector == wrapper.getClassIntrospector()
+ && classIntrospectorClearingCounter == classIntrospector.getClearingCounter()) {
+ cache.put(key, model);
+ }
+ }
+ }
+ return model;
+ } finally {
+ synchronized (sharedLock) {
+ classIntrospectionsInProgress.remove(key);
+ sharedLock.notifyAll();
+ }
+ }
+ }
+
+ void clearCache() {
+ synchronized (wrapper.getSharedIntrospectionLock()) {
+ cache.clear();
+ }
+ }
+
+ void removeFromCache(Class clazz) {
+ synchronized (wrapper.getSharedIntrospectionLock()) {
+ cache.remove(clazz.getName());
+ }
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ protected abstract TemplateModel createModel(Class clazz)
+ throws TemplateModelException;
+
+ protected DefaultObjectWrapper getWrapper() {
+ return wrapper;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassChangeNotifier.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassChangeNotifier.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassChangeNotifier.java
new file mode 100644
index 0000000..52321f0
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassChangeNotifier.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+/**
+ * Reports when the non-private interface of a class was changed to the subscribers.
+ */
+interface ClassChangeNotifier {
+
+ /**
+ * @param classIntrospector Should only be weak-referenced from the monitor object.
+ */
+ void subscribe(ClassIntrospector classIntrospector);
+
+}