| /* |
| * 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 freemarker.core; |
| |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.text.NumberFormat; |
| import java.util.Locale; |
| |
| import freemarker.template.TemplateException; |
| import freemarker.template.utility.StringUtil; |
| |
| /** |
| * An instruction that outputs the value of a numerical expression. |
| */ |
| final class NumericalOutput extends Interpolation { |
| |
| private final Expression expression; |
| private final boolean hasFormat; |
| private final int minFracDigits; |
| private final int maxFracDigits; |
| /** For OutputFormat-based auto-escaping */ |
| private final MarkupOutputFormat autoEscapeOutputFormat; |
| private volatile FormatHolder formatCache; // creating new NumberFormat is slow operation |
| |
| NumericalOutput(Expression expression, MarkupOutputFormat autoEscapeOutputFormat) { |
| this.expression = expression; |
| hasFormat = false; |
| this.minFracDigits = 0; |
| this.maxFracDigits = 0; |
| this.autoEscapeOutputFormat = autoEscapeOutputFormat; |
| } |
| |
| NumericalOutput(Expression expression, |
| int minFracDigits, int maxFracDigits, |
| MarkupOutputFormat autoEscapeOutputFormat) { |
| this.expression = expression; |
| hasFormat = true; |
| this.minFracDigits = minFracDigits; |
| this.maxFracDigits = maxFracDigits; |
| this.autoEscapeOutputFormat = autoEscapeOutputFormat; |
| } |
| |
| @Override |
| TemplateElement[] accept(Environment env) throws TemplateException, IOException { |
| String s = calculateInterpolatedStringOrMarkup(env); |
| Writer out = env.getOut(); |
| if (autoEscapeOutputFormat != null) { |
| autoEscapeOutputFormat.output(s, out); |
| } else { |
| out.write(s); |
| } |
| return null; |
| } |
| |
| @Override |
| protected String calculateInterpolatedStringOrMarkup(Environment env) throws TemplateException { |
| Number num = expression.evalToNumber(env); |
| |
| FormatHolder fmth = formatCache; // atomic sampling |
| if (fmth == null || !fmth.locale.equals(env.getLocale())) { |
| synchronized (this) { |
| fmth = formatCache; |
| if (fmth == null || !fmth.locale.equals(env.getLocale())) { |
| NumberFormat fmt = NumberFormat.getNumberInstance(env.getLocale()); |
| if (hasFormat) { |
| fmt.setMinimumFractionDigits(minFracDigits); |
| fmt.setMaximumFractionDigits(maxFracDigits); |
| } else { |
| fmt.setMinimumFractionDigits(0); |
| fmt.setMaximumFractionDigits(50); |
| } |
| fmt.setGroupingUsed(false); |
| formatCache = new FormatHolder(fmt, env.getLocale()); |
| fmth = formatCache; |
| } |
| } |
| } |
| // We must use Format even if hasFormat == false. |
| // Some locales may use non-Arabic digits, thus replacing the |
| // decimal separator in the result of toString() is not enough. |
| String s = fmth.format.format(num); |
| return s; |
| } |
| |
| @Override |
| protected String dump(boolean canonical, boolean inStringLiteral) { |
| StringBuilder buf = new StringBuilder("#{"); |
| final String exprCF = expression.getCanonicalForm(); |
| buf.append(inStringLiteral ? StringUtil.FTLStringLiteralEnc(exprCF, '"') : exprCF); |
| if (hasFormat) { |
| buf.append(" ; "); |
| buf.append("m"); |
| buf.append(minFracDigits); |
| buf.append("M"); |
| buf.append(maxFracDigits); |
| } |
| buf.append("}"); |
| return buf.toString(); |
| } |
| |
| @Override |
| String getNodeTypeSymbol() { |
| return "#{...}"; |
| } |
| |
| @Override |
| boolean heedsOpeningWhitespace() { |
| return true; |
| } |
| |
| @Override |
| boolean heedsTrailingWhitespace() { |
| return true; |
| } |
| |
| private static class FormatHolder { |
| final NumberFormat format; |
| final Locale locale; |
| |
| FormatHolder(NumberFormat format, Locale locale) { |
| this.format = format; |
| this.locale = locale; |
| } |
| } |
| |
| @Override |
| int getParameterCount() { |
| return 3; |
| } |
| |
| @Override |
| Object getParameterValue(int idx) { |
| switch (idx) { |
| case 0: return expression; |
| case 1: return Integer.valueOf(minFracDigits); |
| case 2: return Integer.valueOf(maxFracDigits); |
| default: throw new IndexOutOfBoundsException(); |
| } |
| } |
| |
| @Override |
| ParameterRole getParameterRole(int idx) { |
| switch (idx) { |
| case 0: return ParameterRole.CONTENT; |
| case 1: return ParameterRole.MINIMUM_DECIMALS; |
| case 2: return ParameterRole.MAXIMUM_DECIMALS; |
| default: throw new IndexOutOfBoundsException(); |
| } |
| } |
| |
| @Override |
| boolean isNestedBlockRepeater() { |
| return false; |
| } |
| } |