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:03 UTC
[20/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/OverloadedMethodsSubset.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
new file mode 100644
index 0000000..e783af8
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
@@ -0,0 +1,402 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._NullArgumentException;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Encapsulates the rules and data structures (including cache) for choosing of the best matching callable member for
+ * a parameter list, from a given set of callable members. There are two subclasses of this, one for non-varags methods,
+ * and one for varargs methods.
+ */
+abstract class OverloadedMethodsSubset {
+
+ /**
+ * Used for an optimization trick to substitute an array of whatever size that contains only 0-s. Since this array
+ * is 0 long, this means that the code that reads the int[] always have to check if the int[] has this value, and
+ * then it has to act like if was all 0-s.
+ */
+ static final int[] ALL_ZEROS_ARRAY = new int[0];
+
+ private static final int[][] ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY = new int[1][];
+ static {
+ ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY[0] = ALL_ZEROS_ARRAY;
+ }
+
+ private Class[/*number of args*/][/*arg index*/] unwrappingHintsByParamCount;
+
+ /**
+ * Tells what types occur at a given parameter position with a bit field. See {@link TypeFlags}.
+ */
+ private int[/*number of args*/][/*arg index*/] typeFlagsByParamCount;
+
+ // TODO: This can cause memory-leak when classes are re-loaded. However, first the genericClassIntrospectionCache
+ // and such need to be oms in this regard.
+ private final Map/*<ArgumentTypes, MaybeEmptyCallableMemberDescriptor>*/ argTypesToMemberDescCache
+ = new ConcurrentHashMap(6, 0.75f, 1);
+
+ private final List/*<ReflectionCallableMemberDescriptor>*/ memberDescs = new LinkedList();
+
+ OverloadedMethodsSubset() {
+ //
+ }
+
+ void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) {
+ memberDescs.add(memberDesc);
+
+ // Warning: Do not modify this array, or put it into unwrappingHintsByParamCount by reference, as the arrays
+ // inside that are modified!
+ final Class[] prepedParamTypes = preprocessParameterTypes(memberDesc);
+ final int paramCount = prepedParamTypes.length; // Must be the same as the length of the original param list
+
+ // Merge these unwrapping hints with the existing table of hints:
+ if (unwrappingHintsByParamCount == null) {
+ unwrappingHintsByParamCount = new Class[paramCount + 1][];
+ unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
+ } else if (unwrappingHintsByParamCount.length <= paramCount) {
+ Class[][] newUnwrappingHintsByParamCount = new Class[paramCount + 1][];
+ System.arraycopy(unwrappingHintsByParamCount, 0, newUnwrappingHintsByParamCount, 0,
+ unwrappingHintsByParamCount.length);
+ unwrappingHintsByParamCount = newUnwrappingHintsByParamCount;
+ unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
+ } else {
+ Class[] unwrappingHints = unwrappingHintsByParamCount[paramCount];
+ if (unwrappingHints == null) {
+ unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone();
+ } else {
+ for (int paramIdx = 0; paramIdx < prepedParamTypes.length; paramIdx++) {
+ // For each parameter list length, we merge the argument type arrays into a single Class[] that
+ // stores the most specific common types for each position. Hence we will possibly use a too generic
+ // hint for the unwrapping. For correct behavior, for each overloaded methods its own parameter
+ // types should be used as a hint. But without unwrapping the arguments, we couldn't select the
+ // overloaded method. So we had to unwrap with all possible target types of each parameter position,
+ // which would be slow and its result would be uncacheable (as we don't have anything usable as
+ // a lookup key). So we just use this compromise.
+ unwrappingHints[paramIdx] = getCommonSupertypeForUnwrappingHint(
+ unwrappingHints[paramIdx], prepedParamTypes[paramIdx]);
+ }
+ }
+ }
+
+ int[] typeFlagsByParamIdx = ALL_ZEROS_ARRAY;
+ // Fill typeFlagsByParamCount (if necessary)
+ for (int paramIdx = 0; paramIdx < paramCount; paramIdx++) {
+ final int typeFlags = TypeFlags.classToTypeFlags(prepedParamTypes[paramIdx]);
+ if (typeFlags != 0) {
+ if (typeFlagsByParamIdx == ALL_ZEROS_ARRAY) {
+ typeFlagsByParamIdx = new int[paramCount];
+ }
+ typeFlagsByParamIdx[paramIdx] = typeFlags;
+ }
+ }
+ mergeInTypesFlags(paramCount, typeFlagsByParamIdx);
+
+ afterWideningUnwrappingHints(prepedParamTypes, typeFlagsByParamIdx);
+ }
+
+ Class[][] getUnwrappingHintsByParamCount() {
+ return unwrappingHintsByParamCount;
+ }
+
+ @SuppressFBWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
+ justification="Locks for member descriptor creation only")
+ final MaybeEmptyCallableMemberDescriptor getMemberDescriptorForArgs(Object[] args, boolean varArg) {
+ ArgumentTypes argTypes = new ArgumentTypes(args);
+ MaybeEmptyCallableMemberDescriptor memberDesc
+ = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes);
+ if (memberDesc == null) {
+ // Synchronized so that we won't unnecessarily invoke the same member desc. for multiple times in parallel.
+ synchronized (argTypesToMemberDescCache) {
+ memberDesc = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes);
+ if (memberDesc == null) {
+ memberDesc = argTypes.getMostSpecific(memberDescs, varArg);
+ argTypesToMemberDescCache.put(argTypes, memberDesc);
+ }
+ }
+ }
+ return memberDesc;
+ }
+
+ Iterator/*<ReflectionCallableMemberDescriptor>*/ getMemberDescriptors() {
+ return memberDescs.iterator();
+ }
+
+ abstract Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc);
+ abstract void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes);
+
+ abstract MaybeEmptyMemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs,
+ DefaultObjectWrapper unwrapper) throws TemplateModelException;
+
+ /**
+ * Returns the most specific common class (or interface) of two parameter types for the purpose of unwrapping.
+ * This is trickier than finding the most specific overlapping superclass of two classes, because:
+ * <ul>
+ * <li>It considers primitive classes as the subclasses of the boxing classes.</li>
+ * <li>If the only common class is {@link Object}, it will try to find a common interface. If there are more
+ * of them, it will start removing those that are known to be uninteresting as unwrapping hints.</li>
+ * </ul>
+ *
+ * @param c1 Parameter type 1
+ * @param c2 Parameter type 2
+ */
+ protected Class getCommonSupertypeForUnwrappingHint(Class c1, Class c2) {
+ if (c1 == c2) return c1;
+ // This also means that the hint for (Integer, Integer) will be Integer, not just Number. This is consistent
+ // with how non-overloaded method hints work.
+
+ // c1 primitive class to boxing class:
+ final boolean c1WasPrim;
+ if (c1.isPrimitive()) {
+ c1 = _ClassUtil.primitiveClassToBoxingClass(c1);
+ c1WasPrim = true;
+ } else {
+ c1WasPrim = false;
+ }
+
+ // c2 primitive class to boxing class:
+ final boolean c2WasPrim;
+ if (c2.isPrimitive()) {
+ c2 = _ClassUtil.primitiveClassToBoxingClass(c2);
+ c2WasPrim = true;
+ } else {
+ c2WasPrim = false;
+ }
+
+ if (c1 == c2) {
+ // If it was like int and Integer, boolean and Boolean, etc., we return the boxing type (as that's the
+ // less specific, because it allows null.)
+ // (If it was two equivalent primitives, we don't get here, because of the 1st line of the method.)
+ return c1;
+ } else if (Number.class.isAssignableFrom(c1) && Number.class.isAssignableFrom(c2)) {
+ // We don't want the unwrapper to convert to a numerical super-type [*] as it's not yet known what the
+ // actual number type of the chosen method will be. We will postpone the actual numerical conversion
+ // until that, especially as some conversions (like oms point to floating point) can be lossy.
+ // * Numerical super-type: Like long > int > short > byte.
+ return Number.class;
+ } else if (c1WasPrim || c2WasPrim) {
+ // At this point these all stand:
+ // - At least one of them was primitive
+ // - No more than one of them was numerical
+ // - They don't have the same wrapper (boxing) class
+ return Object.class;
+ }
+
+ // We never get to this point if buxfixed is true and any of these stands:
+ // - One of classes was a primitive type
+ // - One of classes was a numerical type (either boxing type or primitive)
+
+ Set commonTypes = _MethodUtil.getAssignables(c1, c2);
+ commonTypes.retainAll(_MethodUtil.getAssignables(c2, c1));
+ if (commonTypes.isEmpty()) {
+ // Can happen when at least one of the arguments is an interface, as
+ // they don't have Object at the root of their hierarchy
+ return Object.class;
+ }
+
+ // Gather maximally specific elements. Yes, there can be more than one
+ // because of interfaces. I.e., if you call this method for String.class
+ // and Number.class, you'll have Comparable, Serializable, and Object as
+ // maximal elements.
+ List max = new ArrayList();
+ listCommonTypes: for (Iterator commonTypesIter = commonTypes.iterator(); commonTypesIter.hasNext(); ) {
+ Class clazz = (Class) commonTypesIter.next();
+ for (Iterator maxIter = max.iterator(); maxIter.hasNext(); ) {
+ Class maxClazz = (Class) maxIter.next();
+ if (_MethodUtil.isMoreOrSameSpecificParameterType(maxClazz, clazz, false /*bugfixed [1]*/, 0) != 0) {
+ // clazz can't be maximal, if there's already a more specific or equal maximal than it.
+ continue listCommonTypes;
+ }
+ if (_MethodUtil.isMoreOrSameSpecificParameterType(clazz, maxClazz, false /*bugfixed [1]*/, 0) != 0) {
+ // If it's more specific than a currently maximal element,
+ // that currently maximal is no longer a maximal.
+ maxIter.remove();
+ }
+ // 1: We don't use bugfixed at the "[1]"-marked points because it's slower and doesn't make any
+ // difference here as it's ensured that nor c1 nor c2 is primitive or numerical. The bugfix has only
+ // affected the treatment of primitives and numerical types.
+ }
+ // If we get here, no current maximal is more specific than the
+ // current class, so clazz is a new maximal so far.
+ max.add(clazz);
+ }
+
+ if (max.size() > 1) { // we have an ambiguity
+ // Find the non-interface class
+ for (Iterator it = max.iterator(); it.hasNext(); ) {
+ Class maxCl = (Class) it.next();
+ if (!maxCl.isInterface()) {
+ if (maxCl != Object.class) { // This actually shouldn't ever happen, but to be sure...
+ // If it's not Object, we use it as the most specific
+ return maxCl;
+ } else {
+ // Otherwise remove Object, and we will try with the interfaces
+ it.remove();
+ }
+ }
+ }
+
+ // At this point we only have interfaces left.
+ // Try removing interfaces about which we know that they are useless as unwrapping hints:
+ max.remove(Cloneable.class);
+ if (max.size() > 1) { // Still have an ambiguity...
+ max.remove(Serializable.class);
+ if (max.size() > 1) { // Still had an ambiguity...
+ max.remove(Comparable.class);
+ if (max.size() > 1) {
+ return Object.class; // Still had an ambiguity... no luck.
+ }
+ }
+ }
+ }
+
+ return (Class) max.get(0);
+ }
+
+ /**
+ * Gets the "type flags" of each parameter positions, or {@code null} if there's no method with this parameter
+ * count or if we are in pre-2.3.21 mode, or {@link #ALL_ZEROS_ARRAY} if there were no parameters that turned
+ * on a flag. The returned {@code int}-s are one or more {@link TypeFlags} constants binary "or"-ed together.
+ */
+ final protected int[] getTypeFlags(int paramCount) {
+ return typeFlagsByParamCount != null && typeFlagsByParamCount.length > paramCount
+ ? typeFlagsByParamCount[paramCount]
+ : null;
+ }
+
+ /**
+ * Updates the content of the {@link #typeFlagsByParamCount} field with the parameter type flags of a method.
+ *
+ * @param dstParamCount The parameter count for which we want to merge in the type flags
+ * @param srcTypeFlagsByParamIdx If shorter than {@code dstParamCount}, its last item will be repeated until
+ * dstParamCount length is reached. If longer, the excessive items will be ignored.
+ * Maybe {@link #ALL_ZEROS_ARRAY}. Maybe a 0-length array. Can't be {@code null}.
+ */
+ final protected void mergeInTypesFlags(int dstParamCount, int[] srcTypeFlagsByParamIdx) {
+ _NullArgumentException.check("srcTypesFlagsByParamIdx", srcTypeFlagsByParamIdx);
+
+ // Special case of 0 param count:
+ if (dstParamCount == 0) {
+ if (typeFlagsByParamCount == null) {
+ typeFlagsByParamCount = ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY;
+ } else if (typeFlagsByParamCount != ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY) {
+ typeFlagsByParamCount[0] = ALL_ZEROS_ARRAY;
+ }
+ return;
+ }
+
+ // Ensure that typesFlagsByParamCount[dstParamCount] exists:
+ if (typeFlagsByParamCount == null) {
+ typeFlagsByParamCount = new int[dstParamCount + 1][];
+ } else if (typeFlagsByParamCount.length <= dstParamCount) {
+ int[][] newTypeFlagsByParamCount = new int[dstParamCount + 1][];
+ System.arraycopy(typeFlagsByParamCount, 0, newTypeFlagsByParamCount, 0,
+ typeFlagsByParamCount.length);
+ typeFlagsByParamCount = newTypeFlagsByParamCount;
+ }
+
+ int[] dstTypeFlagsByParamIdx = typeFlagsByParamCount[dstParamCount];
+ if (dstTypeFlagsByParamIdx == null) {
+ // This is the first method added with this number of params => no merging
+
+ if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) {
+ int srcParamCount = srcTypeFlagsByParamIdx.length;
+ dstTypeFlagsByParamIdx = new int[dstParamCount];
+ for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
+ dstTypeFlagsByParamIdx[paramIdx]
+ = srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1];
+ }
+ } else {
+ dstTypeFlagsByParamIdx = ALL_ZEROS_ARRAY;
+ }
+
+ typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx;
+ } else {
+ // dstTypeFlagsByParamIdx != null, so we need to merge into it.
+
+ if (srcTypeFlagsByParamIdx == dstTypeFlagsByParamIdx) {
+ // Used to occur when both are ALL_ZEROS_ARRAY
+ return;
+ }
+
+ // As we will write dstTypeFlagsByParamIdx, it can't remain ALL_ZEROS_ARRAY anymore.
+ if (dstTypeFlagsByParamIdx == ALL_ZEROS_ARRAY && dstParamCount > 0) {
+ dstTypeFlagsByParamIdx = new int[dstParamCount];
+ typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx;
+ }
+
+ for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
+ final int srcParamTypeFlags;
+ if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) {
+ int srcParamCount = srcTypeFlagsByParamIdx.length;
+ srcParamTypeFlags = srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1];
+ } else {
+ srcParamTypeFlags = 0;
+ }
+
+ final int dstParamTypesFlags = dstTypeFlagsByParamIdx[paramIdx];
+ if (dstParamTypesFlags != srcParamTypeFlags) {
+ int mergedTypeFlags = dstParamTypesFlags | srcParamTypeFlags;
+ if ((mergedTypeFlags & TypeFlags.MASK_ALL_NUMERICALS) != 0) {
+ // Must not be set if we don't have numerical type at this index!
+ mergedTypeFlags |= TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT;
+ }
+ dstTypeFlagsByParamIdx[paramIdx] = mergedTypeFlags;
+ }
+ }
+ }
+ }
+
+ protected void forceNumberArgumentsToParameterTypes(
+ Object[] args, Class[] paramTypes, int[] typeFlagsByParamIndex) {
+ final int paramTypesLen = paramTypes.length;
+ final int argsLen = args.length;
+ for (int argIdx = 0; argIdx < argsLen; argIdx++) {
+ final int paramTypeIdx = argIdx < paramTypesLen ? argIdx : paramTypesLen - 1;
+ final int typeFlags = typeFlagsByParamIndex[paramTypeIdx];
+
+ // Forcing the number type can only be interesting if there are numerical parameter types on that index,
+ // and the unwrapping was not to an exact numerical type.
+ if ((typeFlags & TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT) != 0) {
+ final Object arg = args[argIdx];
+ // If arg isn't a number, we can't do any conversions anyway, regardless of the param type.
+ if (arg instanceof Number) {
+ final Class targetType = paramTypes[paramTypeIdx];
+ final Number convertedArg = DefaultObjectWrapper.forceUnwrappedNumberToType((Number) arg, targetType);
+ if (convertedArg != null) {
+ args[argIdx] = convertedArg;
+ }
+ }
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtil.java
new file mode 100644
index 0000000..f501576
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtil.java
@@ -0,0 +1,1289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._NumberUtil;
+
+/**
+ * Everything related to coercion to ambiguous numerical types.
+ */
+class OverloadedNumberUtil {
+
+ // Can't be instantiated
+ private OverloadedNumberUtil() { }
+
+ /**
+ * The lower limit of conversion prices where there's a risk of significant mantissa loss.
+ * The value comes from misc/overloadedNumberRules/prices.ods and generator.ftl.
+ */
+ static final int BIG_MANTISSA_LOSS_PRICE = 4 * 10000;
+
+ /** The highest long that can be stored in double without precision loss: 2**53. */
+ private static final long MAX_DOUBLE_OR_LONG = 9007199254740992L;
+ /** The lowest long that can be stored in double without precision loss: -(2**53). */
+ private static final long MIN_DOUBLE_OR_LONG = -9007199254740992L;
+ private static final int MAX_DOUBLE_OR_LONG_LOG_2 = 53;
+
+ /** The highest long that can be stored in float without precision loss: 2**24. */
+ private static final int MAX_FLOAT_OR_INT = 16777216;
+ /** The lowest long that can be stored in float without precision loss: -(2**24). */
+ private static final int MIN_FLOAT_OR_INT = -16777216;
+ private static final int MAX_FLOAT_OR_INT_LOG_2 = 24;
+ /** Lowest number that we don't thread as possible integer 0. */
+ private static final double LOWEST_ABOVE_ZERO = 0.000001;
+ /** Highest number that we don't thread as possible integer 1. */
+ private static final double HIGHEST_BELOW_ONE = 0.999999;
+
+ /**
+ * Attaches the lowest alternative number type to the parameter number via {@link NumberWithFallbackType}, if
+ * that's useful according the possible target number types. This transformation is applied on the method call
+ * argument list before overloaded method selection.
+ *
+ * <p>Note that as of this writing, this method is only used when
+ * {@link DefaultObjectWrapper#getIncompatibleImprovements()} >= 2.3.21.
+ *
+ * <p>Why's this needed, how it works: Overloaded method selection only selects methods where the <em>type</em>
+ * (not the value!) of the argument is "smaller" or the same as the parameter type. This is similar to how it's in
+ * the Java language. That it only decides based on the parameter type is important because this way
+ * {@link OverloadedMethodsSubset} can cache method lookup decisions using the types as the cache key. Problem is,
+ * since you don't declare the exact numerical types in FTL, and FTL has only a single generic numeric type
+ * anyway, what Java type a {@link TemplateNumberModel} uses internally is often seen as a technical detail of which
+ * the template author can't always keep track of. So we investigate the <em>value</em> of the number too,
+ * then coerce it down without overflow to a type that will match the most overloaded methods. (This
+ * is especially important as FTL often stores numbers in {@link BigDecimal}-s, which will hardly ever match any
+ * method parameters.) We could simply return that number, like {@code Byte(0)} for an {@code Integer(0)},
+ * however, then we would lose the information about what the original type was. The original type is sometimes
+ * important, as in ambiguous situations the method where there's an exact type match should be selected (like,
+ * when someone wants to select an overload explicitly with {@code m(x?int)}). Also, if an overload wins where
+ * the parameter type at the position of the number is {@code Number} or {@code Object} (or {@code Comparable}
+ * etc.), it's expected that we pass in the original value (an {@code Integer} in this example), especially if that
+ * value is the return value of another Java method. That's why we use
+ * {@link NumberWithFallbackType} numerical classes like {@link IntegerOrByte}, which represents both the original
+ * type and the coerced type, all encoded into the class of the value, which is used as the overloaded method lookup
+ * cache key.
+ *
+ * <p>See also: <tt>src\main\misc\overloadedNumberRules\prices.ods</tt>.
+ *
+ * @param num the number to coerce
+ * @param typeFlags the type flags of the target parameter position; see {@link TypeFlags}
+ *
+ * @returns The original number or a {@link NumberWithFallbackType}, depending on the actual value and the types
+ * indicated in the {@code targetNumTypes} parameter.
+ */
+ static Number addFallbackType(final Number num, final int typeFlags) {
+ final Class numClass = num.getClass();
+ if (numClass == BigDecimal.class) {
+ // For now we only support the backward-compatible mode that doesn't prevent roll overs and magnitude loss.
+ // However, we push the overloaded selection to the right direction, so we will at least indicate if the
+ // number has decimals.
+ BigDecimal n = (BigDecimal) num;
+ if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) != 0
+ && (typeFlags & TypeFlags.MASK_KNOWN_NONINTEGERS) != 0
+ && _NumberUtil.isIntegerBigDecimal(n) /* <- can be expensive */) {
+ return new IntegerBigDecimal(n);
+ } else {
+ // Either it was a non-integer, or it didn't mater what it was, as we don't have both integer and
+ // non-integer target types.
+ return n;
+ }
+ } else if (numClass == Integer.class) {
+ int pn = num.intValue();
+ // Note that we try to return the most specific type (i.e., the numerical type with the smallest range), but
+ // only among the types that are possible targets. Like if the only target is int and the value is 1, we
+ // will return Integer 1, not Byte 1, even though byte is automatically converted to int so it would
+ // work too. Why we avoid unnecessarily specific types is that they generate more overloaded method lookup
+ // cache entries, since the cache key is the array of the types of the argument values. So we want as few
+ // permutations as possible.
+ if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
+ return new IntegerOrByte((Integer) num, (byte) pn);
+ } else if ((typeFlags & TypeFlags.SHORT) != 0 && pn <= Short.MAX_VALUE && pn >= Short.MIN_VALUE) {
+ return new IntegerOrShort((Integer) num, (short) pn);
+ } else {
+ return num;
+ }
+ } else if (numClass == Long.class) {
+ final long pn = num.longValue();
+ if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
+ return new LongOrByte((Long) num, (byte) pn);
+ } else if ((typeFlags & TypeFlags.SHORT) != 0 && pn <= Short.MAX_VALUE && pn >= Short.MIN_VALUE) {
+ return new LongOrShort((Long) num, (short) pn);
+ } else if ((typeFlags & TypeFlags.INTEGER) != 0 && pn <= Integer.MAX_VALUE && pn >= Integer.MIN_VALUE) {
+ return new LongOrInteger((Long) num, (int) pn);
+ } else {
+ return num;
+ }
+ } else if (numClass == Double.class) {
+ final double doubleN = num.doubleValue();
+
+ // Can we store it in an integer type?
+ checkIfWholeNumber: do {
+ if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) == 0) break checkIfWholeNumber;
+
+ // There's no hope to be 1-precise outside this region. (Although problems can occur even inside it...)
+ if (doubleN > MAX_DOUBLE_OR_LONG || doubleN < MIN_DOUBLE_OR_LONG) break checkIfWholeNumber;
+
+ long longN = num.longValue();
+ double diff = doubleN - longN;
+ boolean exact; // We will try to ignore precision glitches (like 0.3 - 0.2 - 0.1 = -2.7E-17)
+ if (diff == 0) {
+ exact = true;
+ } else if (diff > 0) {
+ if (diff < LOWEST_ABOVE_ZERO) {
+ exact = false;
+ } else if (diff > HIGHEST_BELOW_ONE) {
+ exact = false;
+ longN++;
+ } else {
+ break checkIfWholeNumber;
+ }
+ } else { // => diff < 0
+ if (diff > -LOWEST_ABOVE_ZERO) {
+ exact = false;
+ } else if (diff < -HIGHEST_BELOW_ONE) {
+ exact = false;
+ longN--;
+ } else {
+ break checkIfWholeNumber;
+ }
+ }
+
+ // If we reach this, it can be treated as a whole number.
+
+ if ((typeFlags & TypeFlags.BYTE) != 0
+ && longN <= Byte.MAX_VALUE && longN >= Byte.MIN_VALUE) {
+ return new DoubleOrByte((Double) num, (byte) longN);
+ } else if ((typeFlags & TypeFlags.SHORT) != 0
+ && longN <= Short.MAX_VALUE && longN >= Short.MIN_VALUE) {
+ return new DoubleOrShort((Double) num, (short) longN);
+ } else if ((typeFlags & TypeFlags.INTEGER) != 0
+ && longN <= Integer.MAX_VALUE && longN >= Integer.MIN_VALUE) {
+ final int intN = (int) longN;
+ return (typeFlags & TypeFlags.FLOAT) != 0 && intN >= MIN_FLOAT_OR_INT && intN <= MAX_FLOAT_OR_INT
+ ? new DoubleOrIntegerOrFloat((Double) num, intN)
+ : new DoubleOrInteger((Double) num, intN);
+ } else if ((typeFlags & TypeFlags.LONG) != 0) {
+ if (exact) {
+ return new DoubleOrLong((Double) num, longN);
+ } else {
+ // We don't deal with non-exact numbers outside the range of int, as we already reach
+ // ULP 2.384185791015625E-7 there.
+ if (longN >= Integer.MIN_VALUE && longN <= Integer.MAX_VALUE) {
+ return new DoubleOrLong((Double) num, longN);
+ } else {
+ break checkIfWholeNumber;
+ }
+ }
+ }
+ // This point is reached if the double value was out of the range of target integer type(s).
+ // Falls through!
+ } while (false);
+ // If we reach this that means that it can't be treated as a whole number.
+
+ if ((typeFlags & TypeFlags.FLOAT) != 0 && doubleN >= -Float.MAX_VALUE && doubleN <= Float.MAX_VALUE) {
+ return new DoubleOrFloat((Double) num);
+ } else {
+ // Simply Double:
+ return num;
+ }
+ } else if (numClass == Float.class) {
+ final float floatN = num.floatValue();
+
+ // Can we store it in an integer type?
+ checkIfWholeNumber: do {
+ if ((typeFlags & TypeFlags.MASK_KNOWN_INTEGERS) == 0) break checkIfWholeNumber;
+
+ // There's no hope to be 1-precise outside this region. (Although problems can occur even inside it...)
+ if (floatN > MAX_FLOAT_OR_INT || floatN < MIN_FLOAT_OR_INT) break checkIfWholeNumber;
+
+ int intN = num.intValue();
+ double diff = floatN - intN;
+ boolean exact; // We will try to ignore precision glitches (like 0.3 - 0.2 - 0.1 = -2.7E-17)
+ if (diff == 0) {
+ exact = true;
+ // We already reach ULP 7.6293945E-6 with bytes, so we don't continue with shorts.
+ } else if (intN >= Byte.MIN_VALUE && intN <= Byte.MAX_VALUE) {
+ if (diff > 0) {
+ if (diff < 0.00001) {
+ exact = false;
+ } else if (diff > 0.99999) {
+ exact = false;
+ intN++;
+ } else {
+ break checkIfWholeNumber;
+ }
+ } else { // => diff < 0
+ if (diff > -0.00001) {
+ exact = false;
+ } else if (diff < -0.99999) {
+ exact = false;
+ intN--;
+ } else {
+ break checkIfWholeNumber;
+ }
+ }
+ } else {
+ break checkIfWholeNumber;
+ }
+
+ // If we reach this, it can be treated as a whole number.
+
+ if ((typeFlags & TypeFlags.BYTE) != 0 && intN <= Byte.MAX_VALUE && intN >= Byte.MIN_VALUE) {
+ return new FloatOrByte((Float) num, (byte) intN);
+ } else if ((typeFlags & TypeFlags.SHORT) != 0 && intN <= Short.MAX_VALUE && intN >= Short.MIN_VALUE) {
+ return new FloatOrShort((Float) num, (short) intN);
+ } else if ((typeFlags & TypeFlags.INTEGER) != 0) {
+ return new FloatOrInteger((Float) num, intN);
+ } else if ((typeFlags & TypeFlags.LONG) != 0) {
+ // We can't even go outside the range of integers, so we don't need Long variation:
+ return exact
+ ? new FloatOrInteger((Float) num, intN)
+ : new FloatOrByte((Float) num, (byte) intN); // as !exact implies (-128..127)
+ }
+ // This point is reached if the float value was out of the range of target integer type(s).
+ // Falls through!
+ } while (false);
+ // If we reach this that means that it can't be treated as a whole number. So it's simply a Float:
+ return num;
+ } else if (numClass == Byte.class) {
+ return num;
+ } else if (numClass == Short.class) {
+ short pn = num.shortValue();
+ if ((typeFlags & TypeFlags.BYTE) != 0 && pn <= Byte.MAX_VALUE && pn >= Byte.MIN_VALUE) {
+ return new ShortOrByte((Short) num, (byte) pn);
+ } else {
+ return num;
+ }
+ } else if (numClass == BigInteger.class) {
+ if ((typeFlags
+ & ((TypeFlags.MASK_KNOWN_INTEGERS | TypeFlags.MASK_KNOWN_NONINTEGERS)
+ ^ (TypeFlags.BIG_INTEGER | TypeFlags.BIG_DECIMAL))) != 0) {
+ BigInteger biNum = (BigInteger) num;
+ final int bitLength = biNum.bitLength(); // Doesn't include sign bit, so it's one less than expected
+ if ((typeFlags & TypeFlags.BYTE) != 0 && bitLength <= 7) {
+ return new BigIntegerOrByte(biNum);
+ } else if ((typeFlags & TypeFlags.SHORT) != 0 && bitLength <= 15) {
+ return new BigIntegerOrShort(biNum);
+ } else if ((typeFlags & TypeFlags.INTEGER) != 0 && bitLength <= 31) {
+ return new BigIntegerOrInteger(biNum);
+ } else if ((typeFlags & TypeFlags.LONG) != 0 && bitLength <= 63) {
+ return new BigIntegerOrLong(biNum);
+ } else if ((typeFlags & TypeFlags.FLOAT) != 0
+ && (bitLength <= MAX_FLOAT_OR_INT_LOG_2
+ || bitLength == MAX_FLOAT_OR_INT_LOG_2 + 1
+ && biNum.getLowestSetBit() >= MAX_FLOAT_OR_INT_LOG_2)) {
+ return new BigIntegerOrFloat(biNum);
+ } else if ((typeFlags & TypeFlags.DOUBLE) != 0
+ && (bitLength <= MAX_DOUBLE_OR_LONG_LOG_2
+ || bitLength == MAX_DOUBLE_OR_LONG_LOG_2 + 1
+ && biNum.getLowestSetBit() >= MAX_DOUBLE_OR_LONG_LOG_2)) {
+ return new BigIntegerOrDouble(biNum);
+ } else {
+ return num;
+ }
+ } else {
+ // No relevant coercion target types; return the BigInteger as is:
+ return num;
+ }
+ } else {
+ // Unknown number type:
+ return num;
+ }
+ }
+
+ interface ByteSource { Byte byteValue(); }
+ interface ShortSource { Short shortValue(); }
+ interface IntegerSource { Integer integerValue(); }
+ interface LongSource { Long longValue(); }
+ interface FloatSource { Float floatValue(); }
+ interface DoubleSource { Double doubleValue(); }
+ interface BigIntegerSource { BigInteger bigIntegerValue(); }
+ interface BigDecimalSource { BigDecimal bigDecimalValue(); }
+
+ /**
+ * Superclass of "Or"-ed numerical types. With an example, a {@code int} 1 has the fallback type {@code byte}, as
+ * that's the smallest type that can store the value, so it can be represented as an {@link IntegerOrByte}.
+ * This is useful as overloaded method selection only examines the type of the arguments, not the value of them,
+ * but with "Or"-ed types we can encode this value-related information into the argument type, hence influencing the
+ * method selection.
+ */
+ abstract static class NumberWithFallbackType extends Number implements Comparable {
+
+ protected abstract Number getSourceNumber();
+
+ @Override
+ public int intValue() {
+ return getSourceNumber().intValue();
+ }
+
+ @Override
+ public long longValue() {
+ return getSourceNumber().longValue();
+ }
+
+ @Override
+ public float floatValue() {
+ return getSourceNumber().floatValue();
+ }
+
+ @Override
+ public double doubleValue() {
+ return getSourceNumber().doubleValue();
+ }
+
+ @Override
+ public byte byteValue() {
+ return getSourceNumber().byteValue();
+ }
+
+ @Override
+ public short shortValue() {
+ return getSourceNumber().shortValue();
+ }
+
+ @Override
+ public int hashCode() {
+ return getSourceNumber().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj != null && getClass() == obj.getClass()) {
+ return getSourceNumber().equals(((NumberWithFallbackType) obj).getSourceNumber());
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getSourceNumber().toString();
+ }
+
+ // We have to implement this, so that if a potential matching method expects a Comparable, which is implemented
+ // by all the supported numerical types, the "Or" type will be a match.
+ @Override
+ public int compareTo(Object o) {
+ Number n = getSourceNumber();
+ if (n instanceof Comparable) {
+ return ((Comparable) n).compareTo(o);
+ } else {
+ throw new ClassCastException(n.getClass().getName() + " is not Comparable.");
+ }
+ }
+
+ }
+
+ /**
+ * Holds a {@link BigDecimal} that stores a whole number. When selecting a overloaded method, FreeMarker tries to
+ * associate {@link BigDecimal} values to parameters of types that can hold non-whole numbers, unless the
+ * {@link BigDecimal} is wrapped into this class, in which case it does the opposite. This mechanism is, however,
+ * too rough to prevent roll overs or magnitude losses. Those are not yet handled for backward compatibility (they
+ * were suppressed earlier too).
+ */
+ static final class IntegerBigDecimal extends NumberWithFallbackType {
+
+ private final BigDecimal n;
+
+ IntegerBigDecimal(BigDecimal n) {
+ this.n = n;
+ }
+
+ @Override
+ protected Number getSourceNumber() {
+ return n;
+ }
+
+ public BigInteger bigIntegerValue() {
+ return n.toBigInteger();
+ }
+
+ }
+
+ static abstract class LongOrSmallerInteger extends NumberWithFallbackType {
+
+ private final Long n;
+
+ protected LongOrSmallerInteger(Long n) {
+ this.n = n;
+ }
+
+ @Override
+ protected Number getSourceNumber() {
+ return n;
+ }
+
+ @Override
+ public long longValue() {
+ return n.longValue();
+ }
+
+ }
+
+ static class LongOrByte extends LongOrSmallerInteger {
+
+ private final byte w;
+
+ LongOrByte(Long n, byte w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public byte byteValue() {
+ return w;
+ }
+
+ }
+
+ static class LongOrShort extends LongOrSmallerInteger {
+
+ private final short w;
+
+ LongOrShort(Long n, short w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public short shortValue() {
+ return w;
+ }
+
+ }
+
+ static class LongOrInteger extends LongOrSmallerInteger {
+
+ private final int w;
+
+ LongOrInteger(Long n, int w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public int intValue() {
+ return w;
+ }
+
+ }
+
+ static abstract class IntegerOrSmallerInteger extends NumberWithFallbackType {
+
+ private final Integer n;
+
+ protected IntegerOrSmallerInteger(Integer n) {
+ this.n = n;
+ }
+
+ @Override
+ protected Number getSourceNumber() {
+ return n;
+ }
+
+ @Override
+ public int intValue() {
+ return n.intValue();
+ }
+
+ }
+
+ static class IntegerOrByte extends IntegerOrSmallerInteger {
+
+ private final byte w;
+
+ IntegerOrByte(Integer n, byte w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public byte byteValue() {
+ return w;
+ }
+
+ }
+
+ static class IntegerOrShort extends IntegerOrSmallerInteger {
+
+ private final short w;
+
+ IntegerOrShort(Integer n, short w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public short shortValue() {
+ return w;
+ }
+
+ }
+
+ static class ShortOrByte extends NumberWithFallbackType {
+
+ private final Short n;
+ private final byte w;
+
+ protected ShortOrByte(Short n, byte w) {
+ this.n = n;
+ this.w = w;
+ }
+
+ @Override
+ protected Number getSourceNumber() {
+ return n;
+ }
+
+ @Override
+ public short shortValue() {
+ return n.shortValue();
+ }
+
+ @Override
+ public byte byteValue() {
+ return w;
+ }
+
+ }
+
+ static abstract class DoubleOrWholeNumber extends NumberWithFallbackType {
+
+ private final Double n;
+
+ protected DoubleOrWholeNumber(Double n) {
+ this.n = n;
+ }
+
+ @Override
+ protected Number getSourceNumber() {
+ return n;
+ }
+
+ @Override
+ public double doubleValue() {
+ return n.doubleValue();
+ }
+
+ }
+
+ static final class DoubleOrByte extends DoubleOrWholeNumber {
+
+ private final byte w;
+
+ DoubleOrByte(Double n, byte w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public byte byteValue() {
+ return w;
+ }
+
+ @Override
+ public short shortValue() {
+ return w;
+ }
+
+ @Override
+ public int intValue() {
+ return w;
+ }
+
+ @Override
+ public long longValue() {
+ return w;
+ }
+
+ }
+
+ static final class DoubleOrShort extends DoubleOrWholeNumber {
+
+ private final short w;
+
+ DoubleOrShort(Double n, short w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public short shortValue() {
+ return w;
+ }
+
+ @Override
+ public int intValue() {
+ return w;
+ }
+
+ @Override
+ public long longValue() {
+ return w;
+ }
+
+ }
+
+ static final class DoubleOrIntegerOrFloat extends DoubleOrWholeNumber {
+
+ private final int w;
+
+ DoubleOrIntegerOrFloat(Double n, int w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public int intValue() {
+ return w;
+ }
+
+ @Override
+ public long longValue() {
+ return w;
+ }
+
+ }
+
+ static final class DoubleOrInteger extends DoubleOrWholeNumber {
+
+ private final int w;
+
+ DoubleOrInteger(Double n, int w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public int intValue() {
+ return w;
+ }
+
+ @Override
+ public long longValue() {
+ return w;
+ }
+
+ }
+
+ static final class DoubleOrLong extends DoubleOrWholeNumber {
+
+ private final long w;
+
+ DoubleOrLong(Double n, long w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public long longValue() {
+ return w;
+ }
+
+ }
+
+ static final class DoubleOrFloat extends NumberWithFallbackType {
+
+ private final Double n;
+
+ DoubleOrFloat(Double n) {
+ this.n = n;
+ }
+
+ @Override
+ public float floatValue() {
+ return n.floatValue();
+ }
+
+ @Override
+ public double doubleValue() {
+ return n.doubleValue();
+ }
+
+ @Override
+ protected Number getSourceNumber() {
+ return n;
+ }
+
+ }
+
+ static abstract class FloatOrWholeNumber extends NumberWithFallbackType {
+
+ private final Float n;
+
+ FloatOrWholeNumber(Float n) {
+ this.n = n;
+ }
+
+ @Override
+ protected Number getSourceNumber() {
+ return n;
+ }
+
+ @Override
+ public float floatValue() {
+ return n.floatValue();
+ }
+
+ }
+
+ static final class FloatOrByte extends FloatOrWholeNumber {
+
+ private final byte w;
+
+ FloatOrByte(Float n, byte w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public byte byteValue() {
+ return w;
+ }
+
+ @Override
+ public short shortValue() {
+ return w;
+ }
+
+ @Override
+ public int intValue() {
+ return w;
+ }
+
+ @Override
+ public long longValue() {
+ return w;
+ }
+
+ }
+
+ static final class FloatOrShort extends FloatOrWholeNumber {
+
+ private final short w;
+
+ FloatOrShort(Float n, short w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public short shortValue() {
+ return w;
+ }
+
+ @Override
+ public int intValue() {
+ return w;
+ }
+
+ @Override
+ public long longValue() {
+ return w;
+ }
+
+ }
+
+ static final class FloatOrInteger extends FloatOrWholeNumber {
+
+ private final int w;
+
+ FloatOrInteger(Float n, int w) {
+ super(n);
+ this.w = w;
+ }
+
+ @Override
+ public int intValue() {
+ return w;
+ }
+
+ @Override
+ public long longValue() {
+ return w;
+ }
+
+ }
+
+ abstract static class BigIntegerOrPrimitive extends NumberWithFallbackType {
+
+ protected final BigInteger n;
+
+ BigIntegerOrPrimitive(BigInteger n) {
+ this.n = n;
+ }
+
+ @Override
+ protected Number getSourceNumber() {
+ return n;
+ }
+
+ }
+
+ final static class BigIntegerOrByte extends BigIntegerOrPrimitive {
+
+ BigIntegerOrByte(BigInteger n) {
+ super(n);
+ }
+
+ }
+
+ final static class BigIntegerOrShort extends BigIntegerOrPrimitive {
+
+ BigIntegerOrShort(BigInteger n) {
+ super(n);
+ }
+
+ }
+
+ final static class BigIntegerOrInteger extends BigIntegerOrPrimitive {
+
+ BigIntegerOrInteger(BigInteger n) {
+ super(n);
+ }
+
+ }
+
+ final static class BigIntegerOrLong extends BigIntegerOrPrimitive {
+
+ BigIntegerOrLong(BigInteger n) {
+ super(n);
+ }
+
+ }
+
+ abstract static class BigIntegerOrFPPrimitive extends BigIntegerOrPrimitive {
+
+ BigIntegerOrFPPrimitive(BigInteger n) {
+ super(n);
+ }
+
+ /** Faster version of {@link BigDecimal#floatValue()}, utilizes that the number known to fit into a long. */
+ @Override
+ public float floatValue() {
+ return n.longValue();
+ }
+
+ /** Faster version of {@link BigDecimal#doubleValue()}, utilizes that the number known to fit into a long. */
+ @Override
+ public double doubleValue() {
+ return n.longValue();
+ }
+
+ }
+
+ final static class BigIntegerOrFloat extends BigIntegerOrFPPrimitive {
+
+ BigIntegerOrFloat(BigInteger n) {
+ super(n);
+ }
+
+ }
+
+ final static class BigIntegerOrDouble extends BigIntegerOrFPPrimitive {
+
+ BigIntegerOrDouble(BigInteger n) {
+ super(n);
+ }
+
+ }
+
+ /**
+ * Returns a non-negative number that indicates how much we want to avoid a given numerical type conversion. Since
+ * we only consider the types here, not the actual value, we always consider the worst case scenario. Like it will
+ * say that converting int to short is not allowed, although int 1 can be converted to byte without loss. To account
+ * for such situations, "Or"-ed types, like {@link IntegerOrByte} has to be used.
+ *
+ * @param fromC the non-primitive type of the argument (with other words, the actual type).
+ * Must be {@link Number} or its subclass. This is possibly an {@link NumberWithFallbackType} subclass.
+ * @param toC the <em>non-primitive</em> type of the target parameter (with other words, the format type).
+ * Must be a {@link Number} subclass, not {@link Number} itself.
+ * Must <em>not</em> be {@link NumberWithFallbackType} or its subclass.
+ *
+ * @return
+ * <p>The possible values are:
+ * <ul>
+ * <li>0: No conversion is needed
+ * <li>[0, 30000): Lossless conversion
+ * <li>[30000, 40000): Smaller precision loss in mantissa is possible.
+ * <li>[40000, 50000): Bigger precision loss in mantissa is possible.
+ * <li>{@link Integer#MAX_VALUE}: Conversion not allowed due to the possibility of magnitude loss or
+ * overflow</li>
+ * </ul>
+ *
+ * <p>At some places, we only care if the conversion is possible, i.e., whether the return value is
+ * {@link Integer#MAX_VALUE} or not. But when multiple overloaded methods have an argument type to which we
+ * could convert to, this number will influence which of those will be chosen.
+ */
+ static int getArgumentConversionPrice(Class fromC, Class toC) {
+ // DO NOT EDIT, generated code!
+ // See: src\main\misc\overloadedNumberRules\README.txt
+ if (toC == fromC) {
+ return 0;
+ } else if (toC == Integer.class) {
+ if (fromC == IntegerBigDecimal.class) return 31003;
+ else if (fromC == BigDecimal.class) return 41003;
+ else if (fromC == Long.class) return Integer.MAX_VALUE;
+ else if (fromC == Double.class) return Integer.MAX_VALUE;
+ else if (fromC == Float.class) return Integer.MAX_VALUE;
+ else if (fromC == Byte.class) return 10003;
+ else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == LongOrInteger.class) return 21003;
+ else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrIntegerOrFloat.class) return 22003;
+ else if (fromC == DoubleOrInteger.class) return 22003;
+ else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
+ else if (fromC == IntegerOrByte.class) return 0;
+ else if (fromC == DoubleOrByte.class) return 22003;
+ else if (fromC == LongOrByte.class) return 21003;
+ else if (fromC == Short.class) return 10003;
+ else if (fromC == LongOrShort.class) return 21003;
+ else if (fromC == ShortOrByte.class) return 10003;
+ else if (fromC == FloatOrInteger.class) return 21003;
+ else if (fromC == FloatOrByte.class) return 21003;
+ else if (fromC == FloatOrShort.class) return 21003;
+ else if (fromC == BigIntegerOrInteger.class) return 16003;
+ else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrByte.class) return 16003;
+ else if (fromC == IntegerOrShort.class) return 0;
+ else if (fromC == DoubleOrShort.class) return 22003;
+ else if (fromC == BigIntegerOrShort.class) return 16003;
+ else return Integer.MAX_VALUE;
+ } else if (toC == Long.class) {
+ if (fromC == Integer.class) return 10004;
+ else if (fromC == IntegerBigDecimal.class) return 31004;
+ else if (fromC == BigDecimal.class) return 41004;
+ else if (fromC == Double.class) return Integer.MAX_VALUE;
+ else if (fromC == Float.class) return Integer.MAX_VALUE;
+ else if (fromC == Byte.class) return 10004;
+ else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == LongOrInteger.class) return 0;
+ else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrIntegerOrFloat.class) return 21004;
+ else if (fromC == DoubleOrInteger.class) return 21004;
+ else if (fromC == DoubleOrLong.class) return 21004;
+ else if (fromC == IntegerOrByte.class) return 10004;
+ else if (fromC == DoubleOrByte.class) return 21004;
+ else if (fromC == LongOrByte.class) return 0;
+ else if (fromC == Short.class) return 10004;
+ else if (fromC == LongOrShort.class) return 0;
+ else if (fromC == ShortOrByte.class) return 10004;
+ else if (fromC == FloatOrInteger.class) return 21004;
+ else if (fromC == FloatOrByte.class) return 21004;
+ else if (fromC == FloatOrShort.class) return 21004;
+ else if (fromC == BigIntegerOrInteger.class) return 15004;
+ else if (fromC == BigIntegerOrLong.class) return 15004;
+ else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrByte.class) return 15004;
+ else if (fromC == IntegerOrShort.class) return 10004;
+ else if (fromC == DoubleOrShort.class) return 21004;
+ else if (fromC == BigIntegerOrShort.class) return 15004;
+ else return Integer.MAX_VALUE;
+ } else if (toC == Double.class) {
+ if (fromC == Integer.class) return 20007;
+ else if (fromC == IntegerBigDecimal.class) return 32007;
+ else if (fromC == BigDecimal.class) return 32007;
+ else if (fromC == Long.class) return 30007;
+ else if (fromC == Float.class) return 10007;
+ else if (fromC == Byte.class) return 20007;
+ else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == LongOrInteger.class) return 21007;
+ else if (fromC == DoubleOrFloat.class) return 0;
+ else if (fromC == DoubleOrIntegerOrFloat.class) return 0;
+ else if (fromC == DoubleOrInteger.class) return 0;
+ else if (fromC == DoubleOrLong.class) return 0;
+ else if (fromC == IntegerOrByte.class) return 20007;
+ else if (fromC == DoubleOrByte.class) return 0;
+ else if (fromC == LongOrByte.class) return 21007;
+ else if (fromC == Short.class) return 20007;
+ else if (fromC == LongOrShort.class) return 21007;
+ else if (fromC == ShortOrByte.class) return 20007;
+ else if (fromC == FloatOrInteger.class) return 10007;
+ else if (fromC == FloatOrByte.class) return 10007;
+ else if (fromC == FloatOrShort.class) return 10007;
+ else if (fromC == BigIntegerOrInteger.class) return 20007;
+ else if (fromC == BigIntegerOrLong.class) return 30007;
+ else if (fromC == BigIntegerOrDouble.class) return 20007;
+ else if (fromC == BigIntegerOrFloat.class) return 20007;
+ else if (fromC == BigIntegerOrByte.class) return 20007;
+ else if (fromC == IntegerOrShort.class) return 20007;
+ else if (fromC == DoubleOrShort.class) return 0;
+ else if (fromC == BigIntegerOrShort.class) return 20007;
+ else return Integer.MAX_VALUE;
+ } else if (toC == Float.class) {
+ if (fromC == Integer.class) return 30006;
+ else if (fromC == IntegerBigDecimal.class) return 33006;
+ else if (fromC == BigDecimal.class) return 33006;
+ else if (fromC == Long.class) return 40006;
+ else if (fromC == Double.class) return Integer.MAX_VALUE;
+ else if (fromC == Byte.class) return 20006;
+ else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == LongOrInteger.class) return 30006;
+ else if (fromC == DoubleOrFloat.class) return 30006;
+ else if (fromC == DoubleOrIntegerOrFloat.class) return 23006;
+ else if (fromC == DoubleOrInteger.class) return 30006;
+ else if (fromC == DoubleOrLong.class) return 40006;
+ else if (fromC == IntegerOrByte.class) return 24006;
+ else if (fromC == DoubleOrByte.class) return 23006;
+ else if (fromC == LongOrByte.class) return 24006;
+ else if (fromC == Short.class) return 20006;
+ else if (fromC == LongOrShort.class) return 24006;
+ else if (fromC == ShortOrByte.class) return 20006;
+ else if (fromC == FloatOrInteger.class) return 0;
+ else if (fromC == FloatOrByte.class) return 0;
+ else if (fromC == FloatOrShort.class) return 0;
+ else if (fromC == BigIntegerOrInteger.class) return 30006;
+ else if (fromC == BigIntegerOrLong.class) return 40006;
+ else if (fromC == BigIntegerOrDouble.class) return 40006;
+ else if (fromC == BigIntegerOrFloat.class) return 24006;
+ else if (fromC == BigIntegerOrByte.class) return 24006;
+ else if (fromC == IntegerOrShort.class) return 24006;
+ else if (fromC == DoubleOrShort.class) return 23006;
+ else if (fromC == BigIntegerOrShort.class) return 24006;
+ else return Integer.MAX_VALUE;
+ } else if (toC == Byte.class) {
+ if (fromC == Integer.class) return Integer.MAX_VALUE;
+ else if (fromC == IntegerBigDecimal.class) return 35001;
+ else if (fromC == BigDecimal.class) return 45001;
+ else if (fromC == Long.class) return Integer.MAX_VALUE;
+ else if (fromC == Double.class) return Integer.MAX_VALUE;
+ else if (fromC == Float.class) return Integer.MAX_VALUE;
+ else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == LongOrInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrIntegerOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
+ else if (fromC == IntegerOrByte.class) return 22001;
+ else if (fromC == DoubleOrByte.class) return 25001;
+ else if (fromC == LongOrByte.class) return 23001;
+ else if (fromC == Short.class) return Integer.MAX_VALUE;
+ else if (fromC == LongOrShort.class) return Integer.MAX_VALUE;
+ else if (fromC == ShortOrByte.class) return 21001;
+ else if (fromC == FloatOrInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == FloatOrByte.class) return 23001;
+ else if (fromC == FloatOrShort.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrByte.class) return 18001;
+ else if (fromC == IntegerOrShort.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrShort.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrShort.class) return Integer.MAX_VALUE;
+ else return Integer.MAX_VALUE;
+ } else if (toC == Short.class) {
+ if (fromC == Integer.class) return Integer.MAX_VALUE;
+ else if (fromC == IntegerBigDecimal.class) return 34002;
+ else if (fromC == BigDecimal.class) return 44002;
+ else if (fromC == Long.class) return Integer.MAX_VALUE;
+ else if (fromC == Double.class) return Integer.MAX_VALUE;
+ else if (fromC == Float.class) return Integer.MAX_VALUE;
+ else if (fromC == Byte.class) return 10002;
+ else if (fromC == BigInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == LongOrInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrIntegerOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrLong.class) return Integer.MAX_VALUE;
+ else if (fromC == IntegerOrByte.class) return 21002;
+ else if (fromC == DoubleOrByte.class) return 24002;
+ else if (fromC == LongOrByte.class) return 22002;
+ else if (fromC == LongOrShort.class) return 22002;
+ else if (fromC == ShortOrByte.class) return 0;
+ else if (fromC == FloatOrInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == FloatOrByte.class) return 22002;
+ else if (fromC == FloatOrShort.class) return 22002;
+ else if (fromC == BigIntegerOrInteger.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrLong.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrDouble.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == BigIntegerOrByte.class) return 17002;
+ else if (fromC == IntegerOrShort.class) return 21002;
+ else if (fromC == DoubleOrShort.class) return 24002;
+ else if (fromC == BigIntegerOrShort.class) return 17002;
+ else return Integer.MAX_VALUE;
+ } else if (toC == BigDecimal.class) {
+ if (fromC == Integer.class) return 20008;
+ else if (fromC == IntegerBigDecimal.class) return 0;
+ else if (fromC == Long.class) return 20008;
+ else if (fromC == Double.class) return 20008;
+ else if (fromC == Float.class) return 20008;
+ else if (fromC == Byte.class) return 20008;
+ else if (fromC == BigInteger.class) return 10008;
+ else if (fromC == LongOrInteger.class) return 20008;
+ else if (fromC == DoubleOrFloat.class) return 20008;
+ else if (fromC == DoubleOrIntegerOrFloat.class) return 20008;
+ else if (fromC == DoubleOrInteger.class) return 20008;
+ else if (fromC == DoubleOrLong.class) return 20008;
+ else if (fromC == IntegerOrByte.class) return 20008;
+ else if (fromC == DoubleOrByte.class) return 20008;
+ else if (fromC == LongOrByte.class) return 20008;
+ else if (fromC == Short.class) return 20008;
+ else if (fromC == LongOrShort.class) return 20008;
+ else if (fromC == ShortOrByte.class) return 20008;
+ else if (fromC == FloatOrInteger.class) return 20008;
+ else if (fromC == FloatOrByte.class) return 20008;
+ else if (fromC == FloatOrShort.class) return 20008;
+ else if (fromC == BigIntegerOrInteger.class) return 10008;
+ else if (fromC == BigIntegerOrLong.class) return 10008;
+ else if (fromC == BigIntegerOrDouble.class) return 10008;
+ else if (fromC == BigIntegerOrFloat.class) return 10008;
+ else if (fromC == BigIntegerOrByte.class) return 10008;
+ else if (fromC == IntegerOrShort.class) return 20008;
+ else if (fromC == DoubleOrShort.class) return 20008;
+ else if (fromC == BigIntegerOrShort.class) return 10008;
+ else return Integer.MAX_VALUE;
+ } else if (toC == BigInteger.class) {
+ if (fromC == Integer.class) return 10005;
+ else if (fromC == IntegerBigDecimal.class) return 10005;
+ else if (fromC == BigDecimal.class) return 40005;
+ else if (fromC == Long.class) return 10005;
+ else if (fromC == Double.class) return Integer.MAX_VALUE;
+ else if (fromC == Float.class) return Integer.MAX_VALUE;
+ else if (fromC == Byte.class) return 10005;
+ else if (fromC == LongOrInteger.class) return 10005;
+ else if (fromC == DoubleOrFloat.class) return Integer.MAX_VALUE;
+ else if (fromC == DoubleOrIntegerOrFloat.class) return 21005;
+ else if (fromC == DoubleOrInteger.class) return 21005;
+ else if (fromC == DoubleOrLong.class) return 21005;
+ else if (fromC == IntegerOrByte.class) return 10005;
+ else if (fromC == DoubleOrByte.class) return 21005;
+ else if (fromC == LongOrByte.class) return 10005;
+ else if (fromC == Short.class) return 10005;
+ else if (fromC == LongOrShort.class) return 10005;
+ else if (fromC == ShortOrByte.class) return 10005;
+ else if (fromC == FloatOrInteger.class) return 25005;
+ else if (fromC == FloatOrByte.class) return 25005;
+ else if (fromC == FloatOrShort.class) return 25005;
+ else if (fromC == BigIntegerOrInteger.class) return 0;
+ else if (fromC == BigIntegerOrLong.class) return 0;
+ else if (fromC == BigIntegerOrDouble.class) return 0;
+ else if (fromC == BigIntegerOrFloat.class) return 0;
+ else if (fromC == BigIntegerOrByte.class) return 0;
+ else if (fromC == IntegerOrShort.class) return 10005;
+ else if (fromC == DoubleOrShort.class) return 21005;
+ else if (fromC == BigIntegerOrShort.class) return 0;
+ else return Integer.MAX_VALUE;
+ } else {
+ // Unknown toC; we don't know how to convert to it:
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ static int compareNumberTypeSpecificity(Class c1, Class c2) {
+ // DO NOT EDIT, generated code!
+ // See: src\main\misc\overloadedNumberRules\README.txt
+ c1 = _ClassUtil.primitiveClassToBoxingClass(c1);
+ c2 = _ClassUtil.primitiveClassToBoxingClass(c2);
+
+ if (c1 == c2) return 0;
+
+ if (c1 == Integer.class) {
+ if (c2 == Long.class) return 4 - 3;
+ if (c2 == Double.class) return 7 - 3;
+ if (c2 == Float.class) return 6 - 3;
+ if (c2 == Byte.class) return 1 - 3;
+ if (c2 == Short.class) return 2 - 3;
+ if (c2 == BigDecimal.class) return 8 - 3;
+ if (c2 == BigInteger.class) return 5 - 3;
+ return 0;
+ }
+ if (c1 == Long.class) {
+ if (c2 == Integer.class) return 3 - 4;
+ if (c2 == Double.class) return 7 - 4;
+ if (c2 == Float.class) return 6 - 4;
+ if (c2 == Byte.class) return 1 - 4;
+ if (c2 == Short.class) return 2 - 4;
+ if (c2 == BigDecimal.class) return 8 - 4;
+ if (c2 == BigInteger.class) return 5 - 4;
+ return 0;
+ }
+ if (c1 == Double.class) {
+ if (c2 == Integer.class) return 3 - 7;
+ if (c2 == Long.class) return 4 - 7;
+ if (c2 == Float.class) return 6 - 7;
+ if (c2 == Byte.class) return 1 - 7;
+ if (c2 == Short.class) return 2 - 7;
+ if (c2 == BigDecimal.class) return 8 - 7;
+ if (c2 == BigInteger.class) return 5 - 7;
+ return 0;
+ }
+ if (c1 == Float.class) {
+ if (c2 == Integer.class) return 3 - 6;
+ if (c2 == Long.class) return 4 - 6;
+ if (c2 == Double.class) return 7 - 6;
+ if (c2 == Byte.class) return 1 - 6;
+ if (c2 == Short.class) return 2 - 6;
+ if (c2 == BigDecimal.class) return 8 - 6;
+ if (c2 == BigInteger.class) return 5 - 6;
+ return 0;
+ }
+ if (c1 == Byte.class) {
+ if (c2 == Integer.class) return 3 - 1;
+ if (c2 == Long.class) return 4 - 1;
+ if (c2 == Double.class) return 7 - 1;
+ if (c2 == Float.class) return 6 - 1;
+ if (c2 == Short.class) return 2 - 1;
+ if (c2 == BigDecimal.class) return 8 - 1;
+ if (c2 == BigInteger.class) return 5 - 1;
+ return 0;
+ }
+ if (c1 == Short.class) {
+ if (c2 == Integer.class) return 3 - 2;
+ if (c2 == Long.class) return 4 - 2;
+ if (c2 == Double.class) return 7 - 2;
+ if (c2 == Float.class) return 6 - 2;
+ if (c2 == Byte.class) return 1 - 2;
+ if (c2 == BigDecimal.class) return 8 - 2;
+ if (c2 == BigInteger.class) return 5 - 2;
+ return 0;
+ }
+ if (c1 == BigDecimal.class) {
+ if (c2 == Integer.class) return 3 - 8;
+ if (c2 == Long.class) return 4 - 8;
+ if (c2 == Double.class) return 7 - 8;
+ if (c2 == Float.class) return 6 - 8;
+ if (c2 == Byte.class) return 1 - 8;
+ if (c2 == Short.class) return 2 - 8;
+ if (c2 == BigInteger.class) return 5 - 8;
+ return 0;
+ }
+ if (c1 == BigInteger.class) {
+ if (c2 == Integer.class) return 3 - 5;
+ if (c2 == Long.class) return 4 - 5;
+ if (c2 == Double.class) return 7 - 5;
+ if (c2 == Float.class) return 6 - 5;
+ if (c2 == Byte.class) return 1 - 5;
+ if (c2 == Short.class) return 2 - 5;
+ if (c2 == BigDecimal.class) return 8 - 5;
+ return 0;
+ }
+ return 0;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
new file mode 100644
index 0000000..6547923
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.lang.reflect.Array;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util.BugException;
+
+/**
+ * Stores the varargs methods for a {@link OverloadedMethods} object.
+ */
+class OverloadedVarArgsMethods extends OverloadedMethodsSubset {
+
+ OverloadedVarArgsMethods() {
+ super();
+ }
+
+ /**
+ * Replaces the last parameter type with the array component type of it.
+ */
+ @Override
+ Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) {
+ final Class[] preprocessedParamTypes = memberDesc.getParamTypes().clone();
+ int ln = preprocessedParamTypes.length;
+ final Class varArgsCompType = preprocessedParamTypes[ln - 1].getComponentType();
+ if (varArgsCompType == null) {
+ throw new BugException("Only varargs methods should be handled here");
+ }
+ preprocessedParamTypes[ln - 1] = varArgsCompType;
+ return preprocessedParamTypes;
+ }
+
+ @Override
+ void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) {
+ // Overview
+ // --------
+ //
+ // So far, m(t1, t2...) was treated by the hint widening like m(t1, t2). So now we have to continue hint
+ // widening like if we had further methods:
+ // - m(t1, t2, t2), m(t1, t2, t2, t2), ...
+ // - m(t1), because a varargs array can be 0 long
+ //
+ // But we can't do that for real, because we had to add infinite number of methods. Also, for efficiency we
+ // don't want to invoke unwrappingHintsByParamCount entries at the indices which are still unused.
+ // So we only update the already existing hints. Remember that we already have m(t1, t2) there.
+
+ final int paramCount = paramTypes.length;
+ final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();
+
+ // The case of e(t1, t2), e(t1, t2, t2), e(t1, t2, t2, t2), ..., where e is an *earlierly* added method.
+ // When that was added, this method wasn't added yet, so it had no chance updating the hints of this method,
+ // so we do that now:
+ // FIXME: Only needed if m(t1, t2) was filled an empty slot, otherwise whatever was there was already
+ // widened by the preceding hints, so this will be a no-op.
+ for (int i = paramCount - 1; i >= 0; i--) {
+ final Class[] previousHints = unwrappingHintsByParamCount[i];
+ if (previousHints != null) {
+ widenHintsToCommonSupertypes(
+ paramCount,
+ previousHints, getTypeFlags(i));
+ break; // we only do this for the first hit, as the methods before that has already widened it.
+ }
+ }
+ // The case of e(t1), where e is an *earlier* added method.
+ // When that was added, this method wasn't added yet, so it had no chance updating the hints of this method,
+ // so we do that now:
+ // FIXME: Same as above; it's often unnecessary.
+ if (paramCount + 1 < unwrappingHintsByParamCount.length) {
+ Class[] oneLongerHints = unwrappingHintsByParamCount[paramCount + 1];
+ if (oneLongerHints != null) {
+ widenHintsToCommonSupertypes(
+ paramCount,
+ oneLongerHints, getTypeFlags(paramCount + 1));
+ }
+ }
+
+ // The case of m(t1, t2, t2), m(t1, t2, t2, t2), ..., where m is the currently added method.
+ // Update the longer hints-arrays:
+ for (int i = paramCount + 1; i < unwrappingHintsByParamCount.length; i++) {
+ widenHintsToCommonSupertypes(
+ i,
+ paramTypes, paramNumericalTypes);
+ }
+ // The case of m(t1) where m is the currently added method.
+ // update the one-shorter hints-array:
+ if (paramCount > 0) { // (should be always true, or else it wasn't a varags method)
+ widenHintsToCommonSupertypes(
+ paramCount - 1,
+ paramTypes, paramNumericalTypes);
+ }
+
+ }
+
+ private void widenHintsToCommonSupertypes(
+ int paramCountOfWidened, Class[] wideningTypes, int[] wideningTypeFlags) {
+ final Class[] typesToWiden = getUnwrappingHintsByParamCount()[paramCountOfWidened];
+ if (typesToWiden == null) {
+ return; // no such overload exists; nothing to widen
+ }
+
+ final int typesToWidenLen = typesToWiden.length;
+ final int wideningTypesLen = wideningTypes.length;
+ int min = Math.min(wideningTypesLen, typesToWidenLen);
+ for (int i = 0; i < min; ++i) {
+ typesToWiden[i] = getCommonSupertypeForUnwrappingHint(typesToWiden[i], wideningTypes[i]);
+ }
+ if (typesToWidenLen > wideningTypesLen) {
+ Class varargsComponentType = wideningTypes[wideningTypesLen - 1];
+ for (int i = wideningTypesLen; i < typesToWidenLen; ++i) {
+ typesToWiden[i] = getCommonSupertypeForUnwrappingHint(typesToWiden[i], varargsComponentType);
+ }
+ }
+
+ mergeInTypesFlags(paramCountOfWidened, wideningTypeFlags);
+ }
+
+ @Override
+ MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, DefaultObjectWrapper unwrapper)
+ throws TemplateModelException {
+ if (tmArgs == null) {
+ // null is treated as empty args
+ tmArgs = Collections.EMPTY_LIST;
+ }
+ final int argsLen = tmArgs.size();
+ final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount();
+ final Object[] pojoArgs = new Object[argsLen];
+ int[] typesFlags = null;
+ // Going down starting from methods with args.length + 1 parameters, because we must try to match against a case
+ // where all specified args are fixargs, and we have 0 varargs.
+ outer: for (int paramCount = Math.min(argsLen + 1, unwrappingHintsByParamCount.length - 1); paramCount >= 0; --paramCount) {
+ Class[] unwarappingHints = unwrappingHintsByParamCount[paramCount];
+ if (unwarappingHints == null) {
+ if (paramCount == 0) {
+ return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS;
+ }
+ continue;
+ }
+
+ typesFlags = getTypeFlags(paramCount);
+ if (typesFlags == ALL_ZEROS_ARRAY) {
+ typesFlags = null;
+ }
+
+ // Try to unwrap the arguments
+ Iterator it = tmArgs.iterator();
+ for (int i = 0; i < argsLen; ++i) {
+ int paramIdx = i < paramCount ? i : paramCount - 1;
+ Object pojo = unwrapper.tryUnwrapTo(
+ (TemplateModel) it.next(),
+ unwarappingHints[paramIdx],
+ typesFlags != null ? typesFlags[paramIdx] : 0);
+ if (pojo == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+ continue outer;
+ }
+ pojoArgs[i] = pojo;
+ }
+ break outer;
+ }
+
+ MaybeEmptyCallableMemberDescriptor maybeEmtpyMemberDesc = getMemberDescriptorForArgs(pojoArgs, true);
+ if (maybeEmtpyMemberDesc instanceof CallableMemberDescriptor) {
+ CallableMemberDescriptor memberDesc = (CallableMemberDescriptor) maybeEmtpyMemberDesc;
+ Object[] pojoArgsWithArray;
+ Object argsOrErrorIdx = replaceVarargsSectionWithArray(pojoArgs, tmArgs, memberDesc, unwrapper);
+ if (argsOrErrorIdx instanceof Object[]) {
+ pojoArgsWithArray = (Object[]) argsOrErrorIdx;
+ } else {
+ return EmptyMemberAndArguments.noCompatibleOverload(((Integer) argsOrErrorIdx).intValue());
+ }
+ if (typesFlags != null) {
+ // Note that overloaded method selection has already accounted for overflow errors when the method
+ // was selected. So this forced conversion shouldn't cause such corruption. Except, conversion from
+ // BigDecimal is allowed to overflow for backward-compatibility.
+ forceNumberArgumentsToParameterTypes(pojoArgsWithArray, memberDesc.getParamTypes(), typesFlags);
+ }
+ return new MemberAndArguments(memberDesc, pojoArgsWithArray);
+ } else {
+ return EmptyMemberAndArguments.from((EmptyCallableMemberDescriptor) maybeEmtpyMemberDesc, pojoArgs);
+ }
+ }
+
+ /**
+ * Converts a flat argument list to one where the last argument is an array that collects the varargs, also
+ * re-unwraps the varargs to the component type. Note that this couldn't be done until we had the concrete
+ * member selected.
+ *
+ * @return An {@code Object[]} if everything went well, or an {@code Integer} the
+ * order (1-based index) of the argument that couldn't be unwrapped.
+ */
+ private Object replaceVarargsSectionWithArray(
+ Object[] args, List modelArgs, CallableMemberDescriptor memberDesc, DefaultObjectWrapper unwrapper)
+ throws TemplateModelException {
+ final Class[] paramTypes = memberDesc.getParamTypes();
+ final int paramCount = paramTypes.length;
+ final Class varArgsCompType = paramTypes[paramCount - 1].getComponentType();
+ final int totalArgCount = args.length;
+ final int fixArgCount = paramCount - 1;
+ if (args.length != paramCount) {
+ Object[] packedArgs = new Object[paramCount];
+ System.arraycopy(args, 0, packedArgs, 0, fixArgCount);
+ Object varargs = Array.newInstance(varArgsCompType, totalArgCount - fixArgCount);
+ for (int i = fixArgCount; i < totalArgCount; ++i) {
+ Object val = unwrapper.tryUnwrapTo((TemplateModel) modelArgs.get(i), varArgsCompType);
+ if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+ return Integer.valueOf(i + 1);
+ }
+ Array.set(varargs, i - fixArgCount, val);
+ }
+ packedArgs[fixArgCount] = varargs;
+ return packedArgs;
+ } else {
+ Object val = unwrapper.tryUnwrapTo((TemplateModel) modelArgs.get(fixArgCount), varArgsCompType);
+ if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
+ return Integer.valueOf(fixArgCount + 1);
+ }
+ Object array = Array.newInstance(varArgsCompType, 1);
+ Array.set(array, 0, val);
+ args[fixArgCount] = array;
+ return args;
+ }
+ }
+
+}
\ No newline at end of file