http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.java new file mode 100644 index 0000000..6ffe937 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedNumberUtils.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._ClassUtils; +import org.apache.freemarker.core.util._NumberUtils; + +/** + * Everything related to coercion to ambiguous numerical types. + */ +class OverloadedNumberUtils { + + // Can't be instantiated + private OverloadedNumberUtils() { } + + /** + * 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 + && _NumberUtils.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 = _ClassUtils.primitiveClassToBoxingClass(c1); + c2 = _ClassUtils.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/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java index eb2ade5..afa1cb1 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java @@ -64,7 +64,7 @@ final class ReflectionCallableMemberDescriptor extends CallableMemberDescriptor @Override String getDeclaration() { - return _MethodUtil.toString(member); + return _MethodUtils.toString(member); } @Override @@ -79,7 +79,7 @@ final class ReflectionCallableMemberDescriptor extends CallableMemberDescriptor @Override boolean isVarargs() { - return _MethodUtil.isVarargs(member); + return _MethodUtils.isVarargs(member); } @Override http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java index 12ac9e0..c655b93 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java @@ -83,14 +83,14 @@ public class RestrictedObjectWrapper extends DefaultObjectWrapper { throw new IllegalStateException("build() can only be executed once."); } - RestrictedObjectWrapper singleton = DefaultObjectWrapperTCCLSingletonUtil.getSingleton( + RestrictedObjectWrapper singleton = DefaultObjectWrapperTCCLSingletonUtils.getSingleton( this, INSTANCE_CACHE, INSTANCE_CACHE_REF_QUEUE, ConstructorInvoker.INSTANCE); alreadyBuilt = true; return singleton; } private static class ConstructorInvoker - implements DefaultObjectWrapperTCCLSingletonUtil._ConstructorInvoker<RestrictedObjectWrapper, Builder> { + implements DefaultObjectWrapperTCCLSingletonUtils._ConstructorInvoker<RestrictedObjectWrapper, Builder> { private static final ConstructorInvoker INSTANCE = new ConstructorInvoker(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java index d27d131..6438596 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java @@ -72,7 +72,7 @@ public final class SimpleJavaMethodModel extends SimpleMethod implements JavaMet } catch (TemplateModelException e) { throw e; } catch (Exception e) { - throw _MethodUtil.newInvocationTemplateModelException(object, getMember(), e); + throw _MethodUtils.newInvocationTemplateModelException(object, getMember(), e); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java index cebcaf8..b1d756c 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java @@ -30,7 +30,7 @@ import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; import org.apache.freemarker.core.model.TemplateMarkupOutputModel; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.util._ClassUtil; +import org.apache.freemarker.core.util._ClassUtils; /** * This class is used as a base for non-overloaded method models and for constructors. @@ -54,19 +54,19 @@ class SimpleMethod { if (args == null) { args = _CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY; } - boolean isVarArg = _MethodUtil.isVarargs(member); + boolean isVarArg = _MethodUtils.isVarargs(member); int typesLen = argTypes.length; if (isVarArg) { if (typesLen - 1 > args.length) { throw new _TemplateModelException( - _MethodUtil.invocationErrorMessageStart(member), + _MethodUtils.invocationErrorMessageStart(member), " takes at least ", typesLen - 1, typesLen - 1 == 1 ? " argument" : " arguments", ", but ", args.length, " was given."); } } else if (typesLen != args.length) { throw new _TemplateModelException( - _MethodUtil.invocationErrorMessageStart(member), + _MethodUtils.invocationErrorMessageStart(member), " takes ", typesLen, typesLen == 1 ? " argument" : " arguments", ", but ", args.length, " was given."); } @@ -148,9 +148,9 @@ class SimpleMethod { private TemplateModelException createArgumentTypeMismatchException( int argIdx, TemplateModel argVal, Class<?> targetType) { _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( - _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: Can't convert the ", + _MethodUtils.invocationErrorMessageStart(member), " couldn't be called: Can't convert the ", new _DelayedOrdinal(argIdx + 1), - " argument's value to the target Java type, ", _ClassUtil.getShortClassName(targetType), + " argument's value to the target Java type, ", _ClassUtils.getShortClassName(targetType), ". The type of the actual value was: ", new _DelayedFTLTypeDescription(argVal)); if (argVal instanceof TemplateMarkupOutputModel && (targetType.isAssignableFrom(String.class))) { desc.tip(MARKUP_OUTPUT_TO_STRING_TIP); @@ -160,9 +160,9 @@ class SimpleMethod { private TemplateModelException createNullToPrimitiveArgumentException(int argIdx, Class<?> targetType) { return new _TemplateModelException( - _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: The value of the ", + _MethodUtils.invocationErrorMessageStart(member), " couldn't be called: The value of the ", new _DelayedOrdinal(argIdx + 1), - " argument was null, but the target Java parameter type (", _ClassUtil.getShortClassName(targetType), + " argument was null, but the target Java parameter type (", _ClassUtils.getShortClassName(targetType), ") is primitive and so can't store null."); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java index 1b6d246..f1b54bf 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java @@ -29,7 +29,7 @@ import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; -import org.apache.freemarker.core.util._ClassUtil; +import org.apache.freemarker.core.util._ClassUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,7 +80,7 @@ class UnsafeMethods { NoSuchMethodException { int brace = methodSpec.indexOf('('); int dot = methodSpec.lastIndexOf('.', brace); - Class clazz = _ClassUtil.forName(methodSpec.substring(0, dot)); + Class clazz = _ClassUtils.forName(methodSpec.substring(0, dot)); String methodName = methodSpec.substring(dot + 1, brace); String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 1); StringTokenizer tok = new StringTokenizer(argSpec, ","); @@ -90,7 +90,7 @@ class UnsafeMethods { String argClassName = tok.nextToken(); argTypes[i] = (Class) primClasses.get(argClassName); if (argTypes[i] == null) { - argTypes[i] = _ClassUtil.forName(argClassName); + argTypes[i] = _ClassUtils.forName(argClassName); } } return clazz.getMethod(methodName, argTypes); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java deleted file mode 100644 index 004ebaa..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/_MethodUtil.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.freemarker.core.model.impl; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.HashSet; -import java.util.Set; - -import org.apache.freemarker.core._DelayedConversionToString; -import org.apache.freemarker.core._DelayedJQuote; -import org.apache.freemarker.core._TemplateModelException; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.util.BugException; -import org.apache.freemarker.core.util._ClassUtil; - -/** - * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! - */ -public final class _MethodUtil { - - private _MethodUtil() { - // Not meant to be instantiated - } - - /** - * Determines whether the type given as the 1st argument is convertible to the type given as the 2nd argument - * for method call argument conversion. This follows the rules of the Java reflection-based method call, except - * that since we don't have the value here, a boxed class is never seen as convertible to a primitive type. - * - * @return 0 means {@code false}, non-0 means {@code true}. - * That is, 0 is returned less specificity or incomparable specificity, also when if - * then method was aborted because of {@code ifHigherThan}. - * The absolute value of the returned non-0 number symbolizes how more specific it is: - * <ul> - * <li>1: The two classes are identical</li> - * <li>2: The 1st type is primitive, the 2nd type is the corresponding boxing class</li> - * <li>3: Both classes are numerical, and one is convertible into the other with widening conversion. - * E.g., {@code int} is convertible to {@code long} and {#code double}, hence {@code int} is more - * specific. - * This ignores primitive VS boxed mismatches, except that a boxed class is never seen as - * convertible to a primitive class.</li> - * <li>4: One class is {@code instanceof} of the other, but they aren't identical. - * But unlike in Java, primitive numerical types are {@code instanceof} {@link Number} here.</li> - * </ul> - */ - // TODO Seems that we don't use the full functionality of this anymore, so we could simplify this. See usages. - static int isMoreOrSameSpecificParameterType(final Class<?> specific, final Class<?> generic, boolean bugfixed, - int ifHigherThan) { - if (ifHigherThan >= 4) return 0; - if (generic.isAssignableFrom(specific)) { - // Identity or widening reference conversion: - return generic == specific ? 1 : 4; - } else { - final boolean specificIsPrim = specific.isPrimitive(); - final boolean genericIsPrim = generic.isPrimitive(); - if (specificIsPrim) { - if (genericIsPrim) { - if (ifHigherThan >= 3) return 0; - return isWideningPrimitiveNumberConversion(specific, generic) ? 3 : 0; - } else { // => specificIsPrim && !genericIsPrim - if (bugfixed) { - final Class<?> specificAsBoxed = _ClassUtil.primitiveClassToBoxingClass(specific); - if (specificAsBoxed == generic) { - // A primitive class is more specific than its boxing class, because it can't store null - return 2; - } else if (generic.isAssignableFrom(specificAsBoxed)) { - // Note: This only occurs if `specific` is a primitive numerical, and `generic == Number` - return 4; - } else if (ifHigherThan >= 3) { - return 0; - } else if (Number.class.isAssignableFrom(specificAsBoxed) - && Number.class.isAssignableFrom(generic)) { - return isWideningBoxedNumberConversion(specificAsBoxed, generic) ? 3 : 0; - } else { - return 0; - } - } else { - return 0; - } - } - } else { // => !specificIsPrim - if (ifHigherThan >= 3) return 0; - if (bugfixed && !genericIsPrim - && Number.class.isAssignableFrom(specific) && Number.class.isAssignableFrom(generic)) { - return isWideningBoxedNumberConversion(specific, generic) ? 3 : 0; - } else { - return 0; - } - } - } // of: !generic.isAssignableFrom(specific) - } - - private static boolean isWideningPrimitiveNumberConversion(final Class<?> source, final Class<?> target) { - if (target == Short.TYPE && (source == Byte.TYPE)) { - return true; - } else if (target == Integer.TYPE && - (source == Short.TYPE || source == Byte.TYPE)) { - return true; - } else if (target == Long.TYPE && - (source == Integer.TYPE || source == Short.TYPE || - source == Byte.TYPE)) { - return true; - } else if (target == Float.TYPE && - (source == Long.TYPE || source == Integer.TYPE || - source == Short.TYPE || source == Byte.TYPE)) { - return true; - } else if (target == Double.TYPE && - (source == Float.TYPE || source == Long.TYPE || - source == Integer.TYPE || source == Short.TYPE || - source == Byte.TYPE)) { - return true; - } else { - return false; - } - } - - private static boolean isWideningBoxedNumberConversion(final Class<?> source, final Class<?> target) { - if (target == Short.class && source == Byte.class) { - return true; - } else if (target == Integer.class && - (source == Short.class || source == Byte.class)) { - return true; - } else if (target == Long.class && - (source == Integer.class || source == Short.class || - source == Byte.class)) { - return true; - } else if (target == Float.class && - (source == Long.class || source == Integer.class || - source == Short.class || source == Byte.class)) { - return true; - } else if (target == Double.class && - (source == Float.class || source == Long.class || - source == Integer.class || source == Short.class || - source == Byte.class)) { - return true; - } else { - return false; - } - } - - /** - * Attention, this doesn't handle primitive classes correctly, nor numerical conversions. - */ - static Set<Class<?>> getAssignables(Class<?> c1, Class<?> c2) { - Set<Class<?>> s = new HashSet<>(); - collectAssignables(c1, c2, s); - return s; - } - - private static void collectAssignables(Class<?> c1, Class<?> c2, Set<Class<?>> s) { - if (c1.isAssignableFrom(c2)) { - s.add(c1); - } - Class<?> sc = c1.getSuperclass(); - if (sc != null) { - collectAssignables(sc, c2, s); - } - Class<?>[] itf = c1.getInterfaces(); - for (Class<?> anItf : itf) { - collectAssignables(anItf, c2, s); - } - } - - public static Class<?>[] getParameterTypes(Member member) { - if (member instanceof Method) { - return ((Method) member).getParameterTypes(); - } - if (member instanceof Constructor<?>) { - return ((Constructor<?>) member).getParameterTypes(); - } - throw new IllegalArgumentException("\"member\" must be Method or Constructor"); - } - - static boolean isVarargs(Member member) { - if (member instanceof Method) { - return ((Method) member).isVarArgs(); - } - if (member instanceof Constructor) { - return ((Constructor<?>) member).isVarArgs(); - } - throw new BugException(); - } - - /** - * Returns a more streamlined method or constructor description than {@code Member.toString()} does. - */ - public static String toString(Member member) { - if (!(member instanceof Method || member instanceof Constructor)) { - throw new IllegalArgumentException("\"member\" must be a Method or Constructor"); - } - - StringBuilder sb = new StringBuilder(); - - if ((member.getModifiers() & Modifier.STATIC) != 0) { - sb.append("static "); - } - - String className = _ClassUtil.getShortClassName(member.getDeclaringClass()); - if (className != null) { - sb.append(className); - sb.append('.'); - } - sb.append(member.getName()); - - sb.append('('); - Class<?>[] paramTypes = _MethodUtil.getParameterTypes(member); - for (int i = 0; i < paramTypes.length; i++) { - if (i != 0) sb.append(", "); - String paramTypeDecl = _ClassUtil.getShortClassName(paramTypes[i]); - if (i == paramTypes.length - 1 && paramTypeDecl.endsWith("[]") && _MethodUtil.isVarargs(member)) { - sb.append(paramTypeDecl.substring(0, paramTypeDecl.length() - 2)); - sb.append("..."); - } else { - sb.append(paramTypeDecl); - } - } - sb.append(')'); - - return sb.toString(); - } - - static Object[] invocationErrorMessageStart(Member member) { - return invocationErrorMessageStart(member, member instanceof Constructor); - } - - private static Object[] invocationErrorMessageStart(Object member, boolean isConstructor) { - return new Object[] { "Java ", isConstructor ? "constructor " : "method ", new _DelayedJQuote(member) }; - } - - static TemplateModelException newInvocationTemplateModelException(Object object, Member member, Throwable e) { - return newInvocationTemplateModelException( - object, - member, - (member.getModifiers() & Modifier.STATIC) != 0, - member instanceof Constructor, - e); - } - - static TemplateModelException newInvocationTemplateModelException(Object object, CallableMemberDescriptor callableMemberDescriptor, Throwable e) { - return newInvocationTemplateModelException( - object, - new _DelayedConversionToString(callableMemberDescriptor) { - @Override - protected String doConversion(Object callableMemberDescriptor) { - return ((CallableMemberDescriptor) callableMemberDescriptor).getDeclaration(); - } - }, - callableMemberDescriptor.isStatic(), - callableMemberDescriptor.isConstructor(), - e); - } - - private static TemplateModelException newInvocationTemplateModelException( - Object parentObject, Object member, boolean isStatic, boolean isConstructor, Throwable e) { - while (e instanceof InvocationTargetException) { - Throwable cause = ((InvocationTargetException) e).getTargetException(); - if (cause != null) { - e = cause; - } else { - break; - } - } - - return new _TemplateModelException(e, - invocationErrorMessageStart(member, isConstructor), - " threw an exception", - isStatic || isConstructor ? "" : new Object[] { - " when invoked on ", parentObject.getClass(), " object ", new _DelayedJQuote(parentObject) - }, - "; see cause exception in the Java stack trace."); - } - - /** - * Extracts the JavaBeans property from a reader method name, or returns {@code null} if the method name doesn't - * look like a reader method name. - */ - static String getBeanPropertyNameFromReaderMethodName(String name, Class<?> returnType) { - int start; - if (name.startsWith("get")) { - start = 3; - } else if (returnType == boolean.class && name.startsWith("is")) { - start = 2; - } else { - return null; - } - int ln = name.length(); - - if (start == ln) { - return null; - } - char c1 = name.charAt(start); - - return start + 1 < ln && Character.isUpperCase(name.charAt(start + 1)) && Character.isUpperCase(c1) - ? name.substring(start) // getFOOBar => "FOOBar" (not lower case) according the JavaBeans spec. - : new StringBuilder(ln - start).append(Character.toLowerCase(c1)).append(name, start + 1, ln) - .toString(); - } - -} \ No newline at end of file