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
