http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/AliasTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/AliasTemplateNumberFormatFactory.java b/src/main/java/org/apache/freemarker/core/AliasTemplateNumberFormatFactory.java new file mode 100644 index 0000000..9fcf6d0 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/AliasTemplateNumberFormatFactory.java @@ -0,0 +1,91 @@ +/* + * 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; + +import java.util.Locale; +import java.util.Map; + +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.core.util._LocaleUtil; + +/** + * Creates an alias to another format, so that the format can be referred to with a simple name in the template, rather + * than as a concrete pattern or other kind of format string. + * + * @since 2.3.24 + */ +public final class AliasTemplateNumberFormatFactory extends TemplateNumberFormatFactory { + + private final String defaultTargetFormatString; + private final Map<Locale, String> localizedTargetFormatStrings; + + /** + * @param targetFormatString + * The format string this format will be an alias to + */ + public AliasTemplateNumberFormatFactory(String targetFormatString) { + defaultTargetFormatString = targetFormatString; + localizedTargetFormatStrings = null; + } + + /** + * @param defaultTargetFormatString + * The format string this format will be an alias to if there's no locale-specific format string for the + * requested locale in {@code localizedTargetFormatStrings} + * @param localizedTargetFormatStrings + * Maps {@link Locale}-s to format strings. If the desired locale doesn't occur in the map, a less + * specific locale is tried, repeatedly until only the language part remains. For example, if locale is + * {@code new Locale("en", "US", "Linux")}, then these keys will be attempted untol a match is found, in + * this order: {@code new Locale("en", "US", "Linux")}, {@code new Locale("en", "US")}, + * {@code new Locale("en")}. If there's still no matching key, the value of the + * {@code targetFormatString} will be used. + */ + public AliasTemplateNumberFormatFactory( + String defaultTargetFormatString, Map<Locale, String> localizedTargetFormatStrings) { + this.defaultTargetFormatString = defaultTargetFormatString; + this.localizedTargetFormatStrings = localizedTargetFormatStrings; + } + + @Override + public TemplateNumberFormat get(String params, Locale locale, Environment env) + throws TemplateValueFormatException { + TemplateFormatUtil.checkHasNoParameters(params); + try { + String targetFormatString; + if (localizedTargetFormatStrings != null) { + Locale lookupLocale = locale; + targetFormatString = localizedTargetFormatStrings.get(lookupLocale); + while (targetFormatString == null + && (lookupLocale = _LocaleUtil.getLessSpecificLocale(lookupLocale)) != null) { + targetFormatString = localizedTargetFormatStrings.get(lookupLocale); + } + } else { + targetFormatString = null; + } + if (targetFormatString == null) { + targetFormatString = defaultTargetFormatString; + } + return env.getTemplateNumberFormat(targetFormatString, locale); + } catch (TemplateValueFormatException e) { + throw new AliasTargetTemplateValueFormatException("Failed to create format based on target format string, " + + _StringUtil.jQuote(params) + ". Reason given: " + e.getMessage(), e); + } + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ArithmeticEngine.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ArithmeticEngine.java b/src/main/java/org/apache/freemarker/core/ArithmeticEngine.java new file mode 100644 index 0000000..b3ffe02 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ArithmeticEngine.java @@ -0,0 +1,549 @@ +/* + * 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; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +import org.apache.freemarker.core.util.BugException; +import org.apache.freemarker.core.util._NumberUtil; +import org.apache.freemarker.core.util._StringUtil; + +/** + * Class to perform arithmetic operations. + */ + +public abstract class ArithmeticEngine { + + /** + * Arithmetic engine that converts all numbers to {@link BigDecimal} and + * then operates on them. This is FreeMarker's default arithmetic engine. + */ + public static final BigDecimalEngine BIGDECIMAL_ENGINE = new BigDecimalEngine(); + /** + * Arithmetic engine that uses (more-or-less) the widening conversions of + * Java language to determine the type of result of operation, instead of + * converting everything to BigDecimal up front. + */ + public static final ConservativeEngine CONSERVATIVE_ENGINE = new ConservativeEngine(); + + public abstract int compareNumbers(Number first, Number second) throws TemplateException; + public abstract Number add(Number first, Number second) throws TemplateException; + public abstract Number subtract(Number first, Number second) throws TemplateException; + public abstract Number multiply(Number first, Number second) throws TemplateException; + public abstract Number divide(Number first, Number second) throws TemplateException; + public abstract Number modulus(Number first, Number second) throws TemplateException; + + /** + * Should be able to parse all FTL numerical literals, Java Double toString results, and XML Schema numbers. + * This means these should be parsed successfully, except if the arithmetical engine + * couldn't support the resulting value anyway (such as NaN, infinite, even non-integers): + * {@code -123.45}, {@code 1.5e3}, {@code 1.5E3}, {@code 0005}, {@code +0}, {@code -0}, {@code NaN}, + * {@code INF}, {@code -INF}, {@code Infinity}, {@code -Infinity}. + */ + public abstract Number toNumber(String s); + + protected int minScale = 12; + protected int maxScale = 12; + protected int roundingPolicy = BigDecimal.ROUND_HALF_UP; + + /** + * Sets the minimal scale to use when dividing BigDecimal numbers. Default + * value is 12. + */ + public void setMinScale(int minScale) { + if (minScale < 0) { + throw new IllegalArgumentException("minScale < 0"); + } + this.minScale = minScale; + } + + /** + * Sets the maximal scale to use when multiplying BigDecimal numbers. + * Default value is 100. + */ + public void setMaxScale(int maxScale) { + if (maxScale < minScale) { + throw new IllegalArgumentException("maxScale < minScale"); + } + this.maxScale = maxScale; + } + + public void setRoundingPolicy(int roundingPolicy) { + if (roundingPolicy != BigDecimal.ROUND_CEILING + && roundingPolicy != BigDecimal.ROUND_DOWN + && roundingPolicy != BigDecimal.ROUND_FLOOR + && roundingPolicy != BigDecimal.ROUND_HALF_DOWN + && roundingPolicy != BigDecimal.ROUND_HALF_EVEN + && roundingPolicy != BigDecimal.ROUND_HALF_UP + && roundingPolicy != BigDecimal.ROUND_UNNECESSARY + && roundingPolicy != BigDecimal.ROUND_UP) { + throw new IllegalArgumentException("invalid rounding policy"); + } + + this.roundingPolicy = roundingPolicy; + } + + /** + * This is the default arithmetic engine in FreeMarker. It converts every + * number it receives into {@link BigDecimal}, then operates on these + * converted {@link BigDecimal}s. + */ + public static class BigDecimalEngine + extends + ArithmeticEngine { + @Override + public int compareNumbers(Number first, Number second) { + // We try to find the result based on the sign (+/-/0) first, because: + // - It's much faster than converting to BigDecial, and comparing to 0 is the most common comparison. + // - It doesn't require any type conversions, and thus things like "Infinity > 0" won't fail. + int firstSignum = _NumberUtil.getSignum(first); + int secondSignum = _NumberUtil.getSignum(second); + if (firstSignum != secondSignum) { + return firstSignum < secondSignum ? -1 : (firstSignum > secondSignum ? 1 : 0); + } else if (firstSignum == 0 && secondSignum == 0) { + return 0; + } else { + BigDecimal left = toBigDecimal(first); + BigDecimal right = toBigDecimal(second); + return left.compareTo(right); + } + } + + @Override + public Number add(Number first, Number second) { + BigDecimal left = toBigDecimal(first); + BigDecimal right = toBigDecimal(second); + return left.add(right); + } + + @Override + public Number subtract(Number first, Number second) { + BigDecimal left = toBigDecimal(first); + BigDecimal right = toBigDecimal(second); + return left.subtract(right); + } + + @Override + public Number multiply(Number first, Number second) { + BigDecimal left = toBigDecimal(first); + BigDecimal right = toBigDecimal(second); + BigDecimal result = left.multiply(right); + if (result.scale() > maxScale) { + result = result.setScale(maxScale, roundingPolicy); + } + return result; + } + + @Override + public Number divide(Number first, Number second) { + BigDecimal left = toBigDecimal(first); + BigDecimal right = toBigDecimal(second); + return divide(left, right); + } + + @Override + public Number modulus(Number first, Number second) { + long left = first.longValue(); + long right = second.longValue(); + return Long.valueOf(left % right); + } + + @Override + public Number toNumber(String s) { + return toBigDecimalOrDouble(s); + } + + private BigDecimal divide(BigDecimal left, BigDecimal right) { + int scale1 = left.scale(); + int scale2 = right.scale(); + int scale = Math.max(scale1, scale2); + scale = Math.max(minScale, scale); + return left.divide(right, scale, roundingPolicy); + } + } + + /** + * An arithmetic engine that conservatively widens the operation arguments + * to extent that they can hold the result of the operation. Widening + * conversions occur in following situations: + * <ul> + * <li>byte and short are always widened to int (alike to Java language).</li> + * <li>To preserve magnitude: when operands are of different types, the + * result type is the type of the wider operand.</li> + * <li>to avoid overflows: if add, subtract, or multiply would overflow on + * integer types, the result is widened from int to long, or from long to + * BigInteger.</li> + * <li>to preserve fractional part: if a division of integer types would + * have a fractional part, int and long are converted to double, and + * BigInteger is converted to BigDecimal. An operation on a float and a + * long results in a double. An operation on a float or double and a + * BigInteger results in a BigDecimal.</li> + * </ul> + */ + public static class ConservativeEngine extends ArithmeticEngine { + private static final int INTEGER = 0; + private static final int LONG = 1; + private static final int FLOAT = 2; + private static final int DOUBLE = 3; + private static final int BIGINTEGER = 4; + private static final int BIGDECIMAL = 5; + + private static final Map classCodes = createClassCodesMap(); + + @Override + public int compareNumbers(Number first, Number second) throws TemplateException { + switch(getCommonClassCode(first, second)) { + case INTEGER: { + int n1 = first.intValue(); + int n2 = second.intValue(); + return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1); + } + case LONG: { + long n1 = first.longValue(); + long n2 = second.longValue(); + return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1); + } + case FLOAT: { + float n1 = first.floatValue(); + float n2 = second.floatValue(); + return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1); + } + case DOUBLE: { + double n1 = first.doubleValue(); + double n2 = second.doubleValue(); + return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1); + } + case BIGINTEGER: { + BigInteger n1 = toBigInteger(first); + BigInteger n2 = toBigInteger(second); + return n1.compareTo(n2); + } + case BIGDECIMAL: { + BigDecimal n1 = toBigDecimal(first); + BigDecimal n2 = toBigDecimal(second); + return n1.compareTo(n2); + } + } + // Make the compiler happy. getCommonClassCode() is guaranteed to + // return only above codes, or throw an exception. + throw new Error(); + } + + @Override + public Number add(Number first, Number second) throws TemplateException { + switch(getCommonClassCode(first, second)) { + case INTEGER: { + int n1 = first.intValue(); + int n2 = second.intValue(); + int n = n1 + n2; + return + ((n ^ n1) < 0 && (n ^ n2) < 0) // overflow check + ? Long.valueOf(((long) n1) + n2) + : Integer.valueOf(n); + } + case LONG: { + long n1 = first.longValue(); + long n2 = second.longValue(); + long n = n1 + n2; + return + ((n ^ n1) < 0 && (n ^ n2) < 0) // overflow check + ? toBigInteger(first).add(toBigInteger(second)) + : Long.valueOf(n); + } + case FLOAT: { + return Float.valueOf(first.floatValue() + second.floatValue()); + } + case DOUBLE: { + return Double.valueOf(first.doubleValue() + second.doubleValue()); + } + case BIGINTEGER: { + BigInteger n1 = toBigInteger(first); + BigInteger n2 = toBigInteger(second); + return n1.add(n2); + } + case BIGDECIMAL: { + BigDecimal n1 = toBigDecimal(first); + BigDecimal n2 = toBigDecimal(second); + return n1.add(n2); + } + } + // Make the compiler happy. getCommonClassCode() is guaranteed to + // return only above codes, or throw an exception. + throw new Error(); + } + + @Override + public Number subtract(Number first, Number second) throws TemplateException { + switch(getCommonClassCode(first, second)) { + case INTEGER: { + int n1 = first.intValue(); + int n2 = second.intValue(); + int n = n1 - n2; + return + ((n ^ n1) < 0 && (n ^ ~n2) < 0) // overflow check + ? Long.valueOf(((long) n1) - n2) + : Integer.valueOf(n); + } + case LONG: { + long n1 = first.longValue(); + long n2 = second.longValue(); + long n = n1 - n2; + return + ((n ^ n1) < 0 && (n ^ ~n2) < 0) // overflow check + ? toBigInteger(first).subtract(toBigInteger(second)) + : Long.valueOf(n); + } + case FLOAT: { + return Float.valueOf(first.floatValue() - second.floatValue()); + } + case DOUBLE: { + return Double.valueOf(first.doubleValue() - second.doubleValue()); + } + case BIGINTEGER: { + BigInteger n1 = toBigInteger(first); + BigInteger n2 = toBigInteger(second); + return n1.subtract(n2); + } + case BIGDECIMAL: { + BigDecimal n1 = toBigDecimal(first); + BigDecimal n2 = toBigDecimal(second); + return n1.subtract(n2); + } + } + // Make the compiler happy. getCommonClassCode() is guaranteed to + // return only above codes, or throw an exception. + throw new Error(); + } + + @Override + public Number multiply(Number first, Number second) throws TemplateException { + switch(getCommonClassCode(first, second)) { + case INTEGER: { + int n1 = first.intValue(); + int n2 = second.intValue(); + int n = n1 * n2; + return + n1 == 0 || n / n1 == n2 // overflow check + ? Integer.valueOf(n) + : Long.valueOf(((long) n1) * n2); + } + case LONG: { + long n1 = first.longValue(); + long n2 = second.longValue(); + long n = n1 * n2; + return + n1 == 0L || n / n1 == n2 // overflow check + ? Long.valueOf(n) + : toBigInteger(first).multiply(toBigInteger(second)); + } + case FLOAT: { + return Float.valueOf(first.floatValue() * second.floatValue()); + } + case DOUBLE: { + return Double.valueOf(first.doubleValue() * second.doubleValue()); + } + case BIGINTEGER: { + BigInteger n1 = toBigInteger(first); + BigInteger n2 = toBigInteger(second); + return n1.multiply(n2); + } + case BIGDECIMAL: { + BigDecimal n1 = toBigDecimal(first); + BigDecimal n2 = toBigDecimal(second); + BigDecimal r = n1.multiply(n2); + return r.scale() > maxScale ? r.setScale(maxScale, roundingPolicy) : r; + } + } + // Make the compiler happy. getCommonClassCode() is guaranteed to + // return only above codes, or throw an exception. + throw new Error(); + } + + @Override + public Number divide(Number first, Number second) throws TemplateException { + switch(getCommonClassCode(first, second)) { + case INTEGER: { + int n1 = first.intValue(); + int n2 = second.intValue(); + if (n1 % n2 == 0) { + return Integer.valueOf(n1 / n2); + } + return Double.valueOf(((double) n1) / n2); + } + case LONG: { + long n1 = first.longValue(); + long n2 = second.longValue(); + if (n1 % n2 == 0) { + return Long.valueOf(n1 / n2); + } + return Double.valueOf(((double) n1) / n2); + } + case FLOAT: { + return Float.valueOf(first.floatValue() / second.floatValue()); + } + case DOUBLE: { + return Double.valueOf(first.doubleValue() / second.doubleValue()); + } + case BIGINTEGER: { + BigInteger n1 = toBigInteger(first); + BigInteger n2 = toBigInteger(second); + BigInteger[] divmod = n1.divideAndRemainder(n2); + if (divmod[1].equals(BigInteger.ZERO)) { + return divmod[0]; + } else { + BigDecimal bd1 = new BigDecimal(n1); + BigDecimal bd2 = new BigDecimal(n2); + return bd1.divide(bd2, minScale, roundingPolicy); + } + } + case BIGDECIMAL: { + BigDecimal n1 = toBigDecimal(first); + BigDecimal n2 = toBigDecimal(second); + int scale1 = n1.scale(); + int scale2 = n2.scale(); + int scale = Math.max(scale1, scale2); + scale = Math.max(minScale, scale); + return n1.divide(n2, scale, roundingPolicy); + } + } + // Make the compiler happy. getCommonClassCode() is guaranteed to + // return only above codes, or throw an exception. + throw new Error(); + } + + @Override + public Number modulus(Number first, Number second) throws TemplateException { + switch(getCommonClassCode(first, second)) { + case INTEGER: { + return Integer.valueOf(first.intValue() % second.intValue()); + } + case LONG: { + return Long.valueOf(first.longValue() % second.longValue()); + } + case FLOAT: { + return Float.valueOf(first.floatValue() % second.floatValue()); + } + case DOUBLE: { + return Double.valueOf(first.doubleValue() % second.doubleValue()); + } + case BIGINTEGER: { + BigInteger n1 = toBigInteger(first); + BigInteger n2 = toBigInteger(second); + return n1.mod(n2); + } + case BIGDECIMAL: { + throw new _MiscTemplateException("Can't calculate remainder on BigDecimals"); + } + } + // Make the compiler happy. getCommonClassCode() is guaranteed to + // return only above codes, or throw an exception. + throw new BugException(); + } + + @Override + public Number toNumber(String s) { + Number n = toBigDecimalOrDouble(s); + return n instanceof BigDecimal ? _NumberUtil.optimizeNumberRepresentation(n) : n; + } + + private static Map createClassCodesMap() { + Map map = new HashMap(17); + Integer intcode = Integer.valueOf(INTEGER); + map.put(Byte.class, intcode); + map.put(Short.class, intcode); + map.put(Integer.class, intcode); + map.put(Long.class, Integer.valueOf(LONG)); + map.put(Float.class, Integer.valueOf(FLOAT)); + map.put(Double.class, Integer.valueOf(DOUBLE)); + map.put(BigInteger.class, Integer.valueOf(BIGINTEGER)); + map.put(BigDecimal.class, Integer.valueOf(BIGDECIMAL)); + return map; + } + + private static int getClassCode(Number num) throws TemplateException { + try { + return ((Integer) classCodes.get(num.getClass())).intValue(); + } catch (NullPointerException e) { + if (num == null) { + throw new _MiscTemplateException("The Number object was null."); + } else { + throw new _MiscTemplateException("Unknown number type ", num.getClass().getName()); + } + } + } + + private static int getCommonClassCode(Number num1, Number num2) throws TemplateException { + int c1 = getClassCode(num1); + int c2 = getClassCode(num2); + int c = c1 > c2 ? c1 : c2; + // If BigInteger is combined with a Float or Double, the result is a + // BigDecimal instead of BigInteger in order not to lose the + // fractional parts. If Float is combined with Long, the result is a + // Double instead of Float to preserve the bigger bit width. + switch(c) { + case FLOAT: { + if ((c1 < c2 ? c1 : c2) == LONG) { + return DOUBLE; + } + break; + } + case BIGINTEGER: { + int min = c1 < c2 ? c1 : c2; + if (min == DOUBLE || min == FLOAT) { + return BIGDECIMAL; + } + break; + } + } + return c; + } + + private static BigInteger toBigInteger(Number num) { + return num instanceof BigInteger ? (BigInteger) num : new BigInteger(num.toString()); + } + } + + private static BigDecimal toBigDecimal(Number num) { + try { + return num instanceof BigDecimal ? (BigDecimal) num : new BigDecimal(num.toString()); + } catch (NumberFormatException e) { + // The exception message is useless, so we add a new one: + throw new NumberFormatException("Can't parse this as BigDecimal number: " + _StringUtil.jQuote(num)); + } + } + + private static Number toBigDecimalOrDouble(String s) { + if (s.length() > 2) { + char c = s.charAt(0); + if (c == 'I' && (s.equals("INF") || s.equals("Infinity"))) { + return Double.valueOf(Double.POSITIVE_INFINITY); + } else if (c == 'N' && s.equals("NaN")) { + return Double.valueOf(Double.NaN); + } else if (c == '-' && s.charAt(1) == 'I' && (s.equals("-INF") || s.equals("-Infinity"))) { + return Double.valueOf(Double.NEGATIVE_INFINITY); + } + } + return new BigDecimal(s); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java b/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java new file mode 100644 index 0000000..f477b95 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java @@ -0,0 +1,128 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.impl.SimpleNumber; + +/** + * An operator for arithmetic operations. Note that the + operator is in {@link ASTExpAddOrConcat}, because its + * overloaded (does string concatenation and more). + */ +final class ArithmeticExpression extends ASTExpression { + + static final int TYPE_SUBSTRACTION = 0; + static final int TYPE_MULTIPLICATION = 1; + static final int TYPE_DIVISION = 2; + static final int TYPE_MODULO = 3; + + private static final char[] OPERATOR_IMAGES = new char[] { '-', '*', '/', '%' }; + + private final ASTExpression lho; + private final ASTExpression rho; + private final int operator; + + ArithmeticExpression(ASTExpression lho, ASTExpression rho, int operator) { + this.lho = lho; + this.rho = rho; + this.operator = operator; + } + + @Override + TemplateModel _eval(Environment env) throws TemplateException { + return _eval(env, this, lho.evalToNumber(env), operator, rho.evalToNumber(env)); + } + + static TemplateModel _eval(Environment env, ASTNode parent, Number lhoNumber, int operator, Number rhoNumber) + throws TemplateException, _MiscTemplateException { + ArithmeticEngine ae = EvalUtil.getArithmeticEngine(env, parent); + switch (operator) { + case TYPE_SUBSTRACTION : + return new SimpleNumber(ae.subtract(lhoNumber, rhoNumber)); + case TYPE_MULTIPLICATION : + return new SimpleNumber(ae.multiply(lhoNumber, rhoNumber)); + case TYPE_DIVISION : + return new SimpleNumber(ae.divide(lhoNumber, rhoNumber)); + case TYPE_MODULO : + return new SimpleNumber(ae.modulus(lhoNumber, rhoNumber)); + default: + if (parent instanceof ASTExpression) { + throw new _MiscTemplateException((ASTExpression) parent, + "Unknown operation: ", Integer.valueOf(operator)); + } else { + throw new _MiscTemplateException("Unknown operation: ", Integer.valueOf(operator)); + } + } + } + + @Override + public String getCanonicalForm() { + return lho.getCanonicalForm() + ' ' + getOperatorSymbol(operator) + ' ' + rho.getCanonicalForm(); + } + + @Override + String getNodeTypeSymbol() { + return String.valueOf(getOperatorSymbol(operator)); + } + + static char getOperatorSymbol(int operator) { + return OPERATOR_IMAGES[operator]; + } + + @Override + boolean isLiteral() { + return constantValue != null || (lho.isLiteral() && rho.isLiteral()); + } + + @Override + protected ASTExpression deepCloneWithIdentifierReplaced_inner( + String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) { + return new ArithmeticExpression( + lho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState), + rho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState), + operator); + } + + @Override + int getParameterCount() { + return 3; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return lho; + case 1: return rho; + case 2: return Integer.valueOf(operator); + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + switch (idx) { + case 0: return ParameterRole.LEFT_HAND_OPERAND; + case 1: return ParameterRole.RIGHT_HAND_OPERAND; + case 2: return ParameterRole.AST_NODE_SUBTYPE; + default: throw new IndexOutOfBoundsException(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BackwardCompatibleTemplateNumberFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BackwardCompatibleTemplateNumberFormat.java b/src/main/java/org/apache/freemarker/core/BackwardCompatibleTemplateNumberFormat.java new file mode 100644 index 0000000..de5983b --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BackwardCompatibleTemplateNumberFormat.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * Only exists for emulating pre-2.3.24-IcI {@code ?string} behavior. + * + * @since 2.3.24 + */ +// [FM3] Still needed? +abstract class BackwardCompatibleTemplateNumberFormat extends TemplateNumberFormat { + + abstract String format(Number number) throws UnformattableValueException; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java b/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java new file mode 100644 index 0000000..2d34a42 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java @@ -0,0 +1,70 @@ +/* + * 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; + + +/** + * A range between two integers (maybe 0 long). + */ +final class BoundedRangeModel extends RangeModel { + + private final int step, size; + private final boolean rightAdaptive; + private final boolean affectedByStringSlicingBug; + + /** + * @param inclusiveEnd Tells if the {@code end} index is part of the range. + * @param rightAdaptive Tells if the right end of the range adapts to the size of the sliced value, if otherwise + * it would be bigger than that. + */ + BoundedRangeModel(int begin, int end, boolean inclusiveEnd, boolean rightAdaptive) { + super(begin); + step = begin <= end ? 1 : -1; + size = Math.abs(end - begin) + (inclusiveEnd ? 1 : 0); + this.rightAdaptive = rightAdaptive; + affectedByStringSlicingBug = inclusiveEnd; + } + + @Override + public int size() { + return size; + } + + @Override + int getStep() { + return step; + } + + @Override + boolean isRightUnbounded() { + return false; + } + + @Override + boolean isRightAdaptive() { + return rightAdaptive; + } + + @Override + boolean isAffactedByStringSlicingBug() { + return affectedByStringSlicingBug; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInBannedWhenAutoEscaping.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInBannedWhenAutoEscaping.java b/src/main/java/org/apache/freemarker/core/BuiltInBannedWhenAutoEscaping.java new file mode 100644 index 0000000..e9c3dd6 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInBannedWhenAutoEscaping.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * A string built-in whose usage is banned when auto-escaping with a markup-output format is active. + * This is just a marker; the actual checking is in {@code FTL.jj}. + */ +abstract class BuiltInBannedWhenAutoEscaping extends SpecialBuiltIn { + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForDate.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForDate.java b/src/main/java/org/apache/freemarker/core/BuiltInForDate.java new file mode 100644 index 0000000..47bf910 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForDate.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.util.Date; + +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModel; + +abstract class BuiltInForDate extends ASTExpBuiltIn { + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel model = target.eval(env); + if (model instanceof TemplateDateModel) { + TemplateDateModel tdm = (TemplateDateModel) model; + return calculateResult(EvalUtil.modelToDate(tdm, target), tdm.getDateType(), env); + } else { + throw newNonDateException(env, model, target); + } + } + + /** Override this to implement the built-in. */ + protected abstract TemplateModel calculateResult( + Date date, int dateType, Environment env) + throws TemplateException; + + static TemplateException newNonDateException(Environment env, TemplateModel model, ASTExpression target) + throws InvalidReferenceException { + TemplateException e; + if (model == null) { + e = InvalidReferenceException.getInstance(target, env); + } else { + e = new NonDateException(target, model, "date", env); + } + return e; + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForHashEx.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForHashEx.java b/src/main/java/org/apache/freemarker/core/BuiltInForHashEx.java new file mode 100644 index 0000000..9497365 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForHashEx.java @@ -0,0 +1,55 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateHashModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +abstract class BuiltInForHashEx extends ASTExpBuiltIn { + + @Override + TemplateModel _eval(Environment env) throws TemplateException { + TemplateModel model = target.eval(env); + if (model instanceof TemplateHashModelEx) { + return calculateResult((TemplateHashModelEx) model, env); + } + throw new NonExtendedHashException(target, model, env); + } + + abstract TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env) + throws TemplateModelException, InvalidReferenceException; + + protected InvalidReferenceException newNullPropertyException( + String propertyName, TemplateModel tm, Environment env) { + if (env.getFastInvalidReferenceExceptions()) { + return InvalidReferenceException.FAST_INSTANCE; + } else { + return new InvalidReferenceException( + new _ErrorDescriptionBuilder( + "The exteneded hash (of class ", tm.getClass().getName(), ") has returned null for its \"", + propertyName, + "\" property. This is maybe a bug. The extended hash was returned by this expression:") + .blame(target), + env, this); + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForLegacyEscaping.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForLegacyEscaping.java b/src/main/java/org/apache/freemarker/core/BuiltInForLegacyEscaping.java new file mode 100644 index 0000000..26d64ad --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForLegacyEscaping.java @@ -0,0 +1,47 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateModel; + +/** + * A string built-in whose usage is banned when auto-escaping with a markup-output format is active. + * This is just a marker; the actual checking is in {@code FTL.jj}. + */ +abstract class BuiltInForLegacyEscaping extends BuiltInBannedWhenAutoEscaping { + + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel tm = target.eval(env); + Object moOrStr = EvalUtil.coerceModelToStringOrMarkup(tm, target, null, env); + if (moOrStr instanceof String) { + return calculateResult((String) moOrStr, env); + } else { + TemplateMarkupOutputModel<?> mo = (TemplateMarkupOutputModel<?>) moOrStr; + if (mo.getOutputFormat().isLegacyBuiltInBypassed(key)) { + return mo; + } + throw new NonStringException(target, tm, env); + } + } + + abstract TemplateModel calculateResult(String s, Environment env) throws TemplateException; + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForLoopVariable.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForLoopVariable.java b/src/main/java/org/apache/freemarker/core/BuiltInForLoopVariable.java new file mode 100644 index 0000000..c4f3ed3 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForLoopVariable.java @@ -0,0 +1,48 @@ +/* + * 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; + +import org.apache.freemarker.core.ASTDirList.IterationContext; +import org.apache.freemarker.core.model.TemplateModel; + +abstract class BuiltInForLoopVariable extends SpecialBuiltIn { + + private String loopVarName; + + void bindToLoopVariable(String loopVarName) { + this.loopVarName = loopVarName; + } + + @Override + TemplateModel _eval(Environment env) throws TemplateException { + IterationContext iterCtx = ASTDirList.findEnclosingIterationContext(env, loopVarName); + if (iterCtx == null) { + // The parser should prevent this situation + throw new _MiscTemplateException( + this, env, + "There's no iteration in context that uses loop variable ", new _DelayedJQuote(loopVarName), "."); + } + + return calculateResult(iterCtx, env); + } + + abstract TemplateModel calculateResult(IterationContext iterCtx, Environment env) throws TemplateException; + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForMarkupOutput.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForMarkupOutput.java b/src/main/java/org/apache/freemarker/core/BuiltInForMarkupOutput.java new file mode 100644 index 0000000..a153d5a --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForMarkupOutput.java @@ -0,0 +1,39 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +abstract class BuiltInForMarkupOutput extends ASTExpBuiltIn { + + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel model = target.eval(env); + if (!(model instanceof TemplateMarkupOutputModel)) { + throw new NonMarkupOutputException(target, model, env); + } + return calculateResult((TemplateMarkupOutputModel) model); + } + + protected abstract TemplateModel calculateResult(TemplateMarkupOutputModel model) throws TemplateModelException; + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForNode.java b/src/main/java/org/apache/freemarker/core/BuiltInForNode.java new file mode 100644 index 0000000..569952d --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForNode.java @@ -0,0 +1,39 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNodeModel; + +abstract class BuiltInForNode extends ASTExpBuiltIn { + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel model = target.eval(env); + if (model instanceof TemplateNodeModel) { + return calculateResult((TemplateNodeModel) model, env); + } else { + throw new NonNodeException(target, model, env); + } + } + abstract TemplateModel calculateResult(TemplateNodeModel nodeModel, Environment env) + throws TemplateModelException; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForNodeEx.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForNodeEx.java b/src/main/java/org/apache/freemarker/core/BuiltInForNodeEx.java new file mode 100644 index 0000000..424f6d0 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForNodeEx.java @@ -0,0 +1,37 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNodeModelEx; + +public abstract class BuiltInForNodeEx extends ASTExpBuiltIn { + @Override + TemplateModel _eval(Environment env) throws TemplateException { + TemplateModel model = target.eval(env); + if (model instanceof TemplateNodeModelEx) { + return calculateResult((TemplateNodeModelEx) model, env); + } else { + throw new NonExtendedNodeException(target, model, env); + } + } + abstract TemplateModel calculateResult(TemplateNodeModelEx nodeModel, Environment env) + throws TemplateModelException; +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForNumber.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForNumber.java b/src/main/java/org/apache/freemarker/core/BuiltInForNumber.java new file mode 100644 index 0000000..9cd4cf0 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForNumber.java @@ -0,0 +1,35 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +abstract class BuiltInForNumber extends ASTExpBuiltIn { + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel model = target.eval(env); + return calculateResult(target.modelToNumber(model, env), model); + } + + abstract TemplateModel calculateResult(Number num, TemplateModel model) + throws TemplateModelException; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java b/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java new file mode 100644 index 0000000..0e5d659 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java @@ -0,0 +1,38 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateSequenceModel; + +abstract class BuiltInForSequence extends ASTExpBuiltIn { + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel model = target.eval(env); + if (!(model instanceof TemplateSequenceModel)) { + throw new NonSequenceException(target, model, env); + } + return calculateResult((TemplateSequenceModel) model); + } + abstract TemplateModel calculateResult(TemplateSequenceModel tsm) + throws TemplateModelException; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInForString.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInForString.java b/src/main/java/org/apache/freemarker/core/BuiltInForString.java new file mode 100644 index 0000000..0d1bcaf --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInForString.java @@ -0,0 +1,36 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateModel; + +abstract class BuiltInForString extends ASTExpBuiltIn { + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + return calculateResult(getTargetString(target, env), env); + } + abstract TemplateModel calculateResult(String s, Environment env) throws TemplateException; + + static String getTargetString(ASTExpression target, Environment env) throws TemplateException { + return target.evalAndCoerceToStringOrUnsupportedMarkup(env); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java b/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java new file mode 100644 index 0000000..60910b3 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java @@ -0,0 +1,111 @@ +/* + * 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; + +import java.util.List; + +import org.apache.freemarker.core.Token; + + +abstract class BuiltInWithParseTimeParameters extends SpecialBuiltIn { + + abstract void bindToParameters(List/*<ASTExpression>*/ parameters, Token openParen, Token closeParen) + throws ParseException; + + @Override + public String getCanonicalForm() { + StringBuilder buf = new StringBuilder(); + + buf.append(super.getCanonicalForm()); + + buf.append("("); + List/*<ASTExpression>*/args = getArgumentsAsList(); + int size = args.size(); + for (int i = 0; i < size; i++) { + if (i != 0) { + buf.append(", "); + } + ASTExpression arg = (ASTExpression) args.get(i); + buf.append(arg.getCanonicalForm()); + } + buf.append(")"); + + return buf.toString(); + } + + @Override + String getNodeTypeSymbol() { + return super.getNodeTypeSymbol() + "(...)"; + } + + @Override + int getParameterCount() { + return super.getParameterCount() + getArgumentsCount(); + } + + @Override + Object getParameterValue(int idx) { + final int superParamCnt = super.getParameterCount(); + if (idx < superParamCnt) { + return super.getParameterValue(idx); + } + + final int argIdx = idx - superParamCnt; + return getArgumentParameterValue(argIdx); + } + + @Override + ParameterRole getParameterRole(int idx) { + final int superParamCnt = super.getParameterCount(); + if (idx < superParamCnt) { + return super.getParameterRole(idx); + } + + if (idx - superParamCnt < getArgumentsCount()) { + return ParameterRole.ARGUMENT_VALUE; + } else { + throw new IndexOutOfBoundsException(); + } + } + + protected ParseException newArgumentCountException(String ordinalityDesc, Token openParen, Token closeParen) { + return new ParseException( + "?" + key + "(...) " + ordinalityDesc + " parameters", getTemplate(), + openParen.beginLine, openParen.beginColumn, + closeParen.endLine, closeParen.endColumn); + } + + @Override + protected ASTExpression deepCloneWithIdentifierReplaced_inner( + String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) { + final ASTExpression clone = super.deepCloneWithIdentifierReplaced_inner(replacedIdentifier, replacement, replacementState); + cloneArguments(clone, replacedIdentifier, replacement, replacementState); + return clone; + } + + protected abstract List getArgumentsAsList(); + + protected abstract int getArgumentsCount(); + + protected abstract ASTExpression getArgumentParameterValue(int argIdx); + + protected abstract void cloneArguments(ASTExpression clone, String replacedIdentifier, + ASTExpression replacement, ReplacemenetState replacementState); + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForDates.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForDates.java b/src/main/java/org/apache/freemarker/core/BuiltInsForDates.java new file mode 100644 index 0000000..8c2e392 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInsForDates.java @@ -0,0 +1,212 @@ +/* + * 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; + +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import org.apache.freemarker.core.model.AdapterTemplateModel; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateMethodModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.impl.SimpleDate; +import org.apache.freemarker.core.model.impl.SimpleScalar; +import org.apache.freemarker.core.util.UnrecognizedTimeZoneException; +import org.apache.freemarker.core.util._DateUtil; + +/** + * A holder for built-ins that operate exclusively on date left-hand values. + */ +class BuiltInsForDates { + + static class dateType_if_unknownBI extends ASTExpBuiltIn { + + private final int dateType; + + dateType_if_unknownBI(int dateType) { + this.dateType = dateType; + } + + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel model = target.eval(env); + if (model instanceof TemplateDateModel) { + TemplateDateModel tdm = (TemplateDateModel) model; + int tdmDateType = tdm.getDateType(); + if (tdmDateType != TemplateDateModel.UNKNOWN) { + return tdm; + } + return new SimpleDate(EvalUtil.modelToDate(tdm, target), dateType); + } else { + throw BuiltInForDate.newNonDateException(env, model, target); + } + } + + protected TemplateModel calculateResult(Date date, int dateType, Environment env) throws TemplateException { + // TODO Auto-generated method stub + return null; + } + + } + + /** + * Implements {@code ?iso(timeZone)}. + */ + static class iso_BI extends AbstractISOBI { + + class Result implements TemplateMethodModelEx { + private final Date date; + private final int dateType; + private final Environment env; + + Result(Date date, int dateType, Environment env) { + this.date = date; + this.dateType = dateType; + this.env = env; + } + + @Override + public Object exec(List args) throws TemplateModelException { + checkMethodArgCount(args, 1); + + TemplateModel tzArgTM = (TemplateModel) args.get(0); + TimeZone tzArg; + Object adaptedObj; + if (tzArgTM instanceof AdapterTemplateModel + && (adaptedObj = + ((AdapterTemplateModel) tzArgTM) + .getAdaptedObject(TimeZone.class)) + instanceof TimeZone) { + tzArg = (TimeZone) adaptedObj; + } else if (tzArgTM instanceof TemplateScalarModel) { + String tzName = EvalUtil.modelToString((TemplateScalarModel) tzArgTM, null, null); + try { + tzArg = _DateUtil.getTimeZone(tzName); + } catch (UnrecognizedTimeZoneException e) { + throw new _TemplateModelException( + "The time zone string specified for ?", key, + "(...) is not recognized as a valid time zone name: ", + new _DelayedJQuote(tzName)); + } + } else { + throw MessageUtil.newMethodArgUnexpectedTypeException( + "?" + key, 0, "string or java.util.TimeZone", tzArgTM); + } + + return new SimpleScalar(_DateUtil.dateToISO8601String( + date, + dateType != TemplateDateModel.TIME, + dateType != TemplateDateModel.DATE, + shouldShowOffset(date, dateType, env), + accuracy, + tzArg, + env.getISOBuiltInCalendarFactory())); + } + + } + + iso_BI(Boolean showOffset, int accuracy) { + super(showOffset, accuracy); + } + + @Override + protected TemplateModel calculateResult( + Date date, int dateType, Environment env) + throws TemplateException { + checkDateTypeNotUnknown(dateType); + return new Result(date, dateType, env); + } + + } + + /** + * Implements {@code ?iso_utc} and {@code ?iso_local} variants, but not + * {@code ?iso(timeZone)}. + */ + static class iso_utc_or_local_BI extends AbstractISOBI { + + private final boolean useUTC; + + iso_utc_or_local_BI(Boolean showOffset, int accuracy, boolean useUTC) { + super(showOffset, accuracy); + this.useUTC = useUTC; + } + + @Override + protected TemplateModel calculateResult( + Date date, int dateType, Environment env) + throws TemplateException { + checkDateTypeNotUnknown(dateType); + return new SimpleScalar(_DateUtil.dateToISO8601String( + date, + dateType != TemplateDateModel.TIME, + dateType != TemplateDateModel.DATE, + shouldShowOffset(date, dateType, env), + accuracy, + useUTC + ? _DateUtil.UTC + : env.shouldUseSQLDTTZ(date.getClass()) + ? env.getSQLDateAndTimeTimeZone() + : env.getTimeZone(), + env.getISOBuiltInCalendarFactory())); + } + + } + + // Can't be instantiated + private BuiltInsForDates() { } + + static abstract class AbstractISOBI extends BuiltInForDate { + protected final Boolean showOffset; + protected final int accuracy; + + protected AbstractISOBI(Boolean showOffset, int accuracy) { + this.showOffset = showOffset; + this.accuracy = accuracy; + } + + protected void checkDateTypeNotUnknown(int dateType) + throws TemplateException { + if (dateType == TemplateDateModel.UNKNOWN) { + throw new _MiscTemplateException(new _ErrorDescriptionBuilder( + "The value of the following has unknown date type, but ?", key, + " needs a value where it's known if it's a date (no time part), time, or date-time value:" + ).blame(target).tip(MessageUtil.UNKNOWN_DATE_TYPE_ERROR_TIP)); + } + } + + protected boolean shouldShowOffset(Date date, int dateType, Environment env) { + if (dateType == TemplateDateModel.DATE) { + return false; // ISO 8061 doesn't allow zone for date-only values + } else if (showOffset != null) { + return showOffset.booleanValue(); + } else { + // java.sql.Time values meant to carry calendar field values only, so we don't show offset for them. + return !(date instanceof java.sql.Time); + } + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java b/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java new file mode 100644 index 0000000..cbbffc9 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java @@ -0,0 +1,133 @@ +/* + * 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; + +import java.util.List; + +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateMethodModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * A holder for builtins that deal with null left-hand values. + */ +class BuiltInsForExistenceHandling { + + // Can't be instantiated + private BuiltInsForExistenceHandling() { } + + private static abstract class ExistenceBuiltIn extends ASTExpBuiltIn { + + protected TemplateModel evalMaybeNonexistentTarget(Environment env) throws TemplateException { + TemplateModel tm; + if (target instanceof ASTExpParenthesis) { + boolean lastFIRE = env.setFastInvalidReferenceExceptions(true); + try { + tm = target.eval(env); + } catch (InvalidReferenceException ire) { + tm = null; + } finally { + env.setFastInvalidReferenceExceptions(lastFIRE); + } + } else { + tm = target.eval(env); + } + return tm; + } + + } + + static class defaultBI extends BuiltInsForExistenceHandling.ExistenceBuiltIn { + + @Override + TemplateModel _eval(final Environment env) throws TemplateException { + TemplateModel model = evalMaybeNonexistentTarget(env); + return model == null ? FIRST_NON_NULL_METHOD : new ConstantMethod(model); + } + + private static class ConstantMethod implements TemplateMethodModelEx { + private final TemplateModel constant; + + ConstantMethod(TemplateModel constant) { + this.constant = constant; + } + + @Override + public Object exec(List args) { + return constant; + } + } + + /** + * A method that goes through the arguments one by one and returns + * the first one that is non-null. If all args are null, returns null. + */ + private static final TemplateMethodModelEx FIRST_NON_NULL_METHOD = + new TemplateMethodModelEx() { + @Override + public Object exec(List args) throws TemplateModelException { + int argCnt = args.size(); + if (argCnt == 0) throw MessageUtil.newArgCntError("?default", argCnt, 1, Integer.MAX_VALUE); + for (int i = 0; i < argCnt; i++ ) { + TemplateModel result = (TemplateModel) args.get(i); + if (result != null) return result; + } + return null; + } + }; + } + + static class existsBI extends BuiltInsForExistenceHandling.ExistenceBuiltIn { + @Override + TemplateModel _eval(Environment env) throws TemplateException { + return evalMaybeNonexistentTarget(env) == null ? TemplateBooleanModel.FALSE : TemplateBooleanModel.TRUE; + } + + @Override + boolean evalToBoolean(Environment env) throws TemplateException { + return _eval(env) == TemplateBooleanModel.TRUE; + } + } + + static class has_contentBI extends BuiltInsForExistenceHandling.ExistenceBuiltIn { + @Override + TemplateModel _eval(Environment env) throws TemplateException { + return ASTExpression.isEmpty(evalMaybeNonexistentTarget(env)) + ? TemplateBooleanModel.FALSE + : TemplateBooleanModel.TRUE; + } + + @Override + boolean evalToBoolean(Environment env) throws TemplateException { + return _eval(env) == TemplateBooleanModel.TRUE; + } + } + + static class if_existsBI extends BuiltInsForExistenceHandling.ExistenceBuiltIn { + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel model = evalMaybeNonexistentTarget(env); + return model == null ? TemplateModel.NOTHING : model; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java b/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java new file mode 100644 index 0000000..562a6d2 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java @@ -0,0 +1,59 @@ +/* + * 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; + +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateHashModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.CollectionAndSequence; + +/** + * A holder for builtins that operate exclusively on hash left-hand value. + */ +class BuiltInsForHashes { + + static class keysBI extends BuiltInForHashEx { + + @Override + TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env) + throws TemplateModelException, InvalidReferenceException { + TemplateCollectionModel keys = hashExModel.keys(); + if (keys == null) throw newNullPropertyException("keys", hashExModel, env); + return keys instanceof TemplateSequenceModel ? keys : new CollectionAndSequence(keys); + } + + } + + static class valuesBI extends BuiltInForHashEx { + @Override + TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env) + throws TemplateModelException, InvalidReferenceException { + TemplateCollectionModel values = hashExModel.values(); + if (values == null) throw newNullPropertyException("values", hashExModel, env); + return values instanceof TemplateSequenceModel ? values : new CollectionAndSequence(values); + } + } + + // Can't be instantiated + private BuiltInsForHashes() { } + +}
