http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java new file mode 100644 index 0000000..7766012 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java @@ -0,0 +1,408 @@ +/* + * 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.io.IOException; + +import org.apache.freemarker.core.util._CollectionUtil; +import org.apache.freemarker.core.util._StringUtil; + +/** + * AST node representing static text. + */ +final class ASTStaticText extends ASTElement { + + // We're using char[] instead of String for storing the text block because + // Writer.write(String) involves copying the String contents to a char[] + // using String.getChars(), and then calling Writer.write(char[]). By + // using Writer.write(char[]) directly, we avoid array copying on each + // write. + private char[] text; + private final boolean unparsed; + + public ASTStaticText(String text) { + this(text, false); + } + + public ASTStaticText(String text, boolean unparsed) { + this(text.toCharArray(), unparsed); + } + + ASTStaticText(char[] text, boolean unparsed) { + this.text = text; + this.unparsed = unparsed; + } + + void replaceText(String text) { + this.text = text.toCharArray(); + } + + /** + * Simply outputs the text. + * + * @deprecated This is an internal API; don't call or override it. + */ + @Deprecated + @Override + public ASTElement[] accept(Environment env) + throws IOException { + env.getOut().write(text); + return null; + } + + @Override + protected String dump(boolean canonical) { + if (canonical) { + String text = new String(this.text); + if (unparsed) { + return "<#noparse>" + text + "</#noparse>"; + } + return text; + } else { + return "text " + _StringUtil.jQuote(new String(text)); + } + } + + @Override + String getNodeTypeSymbol() { + return "#text"; + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return new String(text); + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return ParameterRole.CONTENT; + } + + @Override + ASTElement postParseCleanup(boolean stripWhitespace) { + if (text.length == 0) return this; + int openingCharsToStrip = 0, trailingCharsToStrip = 0; + boolean deliberateLeftTrim = deliberateLeftTrim(); + boolean deliberateRightTrim = deliberateRightTrim(); + if (!stripWhitespace || text.length == 0 ) { + return this; + } + ASTElement parentElement = getParent(); + if (isTopLevelTextIfParentIs(parentElement) && previousSibling() == null) { + return this; + } + if (!deliberateLeftTrim) { + trailingCharsToStrip = trailingCharsToStrip(); + } + if (!deliberateRightTrim) { + openingCharsToStrip = openingCharsToStrip(); + } + if (openingCharsToStrip == 0 && trailingCharsToStrip == 0) { + return this; + } + text = substring(text, openingCharsToStrip, text.length - trailingCharsToStrip); + if (openingCharsToStrip > 0) { + beginLine++; + beginColumn = 1; + } + if (trailingCharsToStrip > 0) { + endColumn = 0; + } + return this; + } + + /** + * Scans forward the nodes on the same line to see whether there is a + * deliberate left trim in effect. Returns true if the left trim was present. + */ + private boolean deliberateLeftTrim() { + boolean result = false; + for (ASTElement elem = nextTerminalNode(); + elem != null && elem.beginLine == endLine; + elem = elem.nextTerminalNode()) { + if (elem instanceof ASTDirTOrTrOrTl) { + ASTDirTOrTrOrTl ti = (ASTDirTOrTrOrTl) elem; + if (!ti.left && !ti.right) { + result = true; + } + if (ti.left) { + result = true; + int lastNewLineIndex = lastNewLineIndex(); + if (lastNewLineIndex >= 0 || beginColumn == 1) { + char[] firstPart = substring(text, 0, lastNewLineIndex + 1); + char[] lastLine = substring(text, 1 + lastNewLineIndex); + if (_StringUtil.isTrimmableToEmpty(lastLine)) { + text = firstPart; + endColumn = 0; + } else { + int i = 0; + while (Character.isWhitespace(lastLine[i])) { + i++; + } + char[] printablePart = substring(lastLine, i); + text = concat(firstPart, printablePart); + } + } + } + } + } + return result; + } + + /** + * Checks for the presence of a t or rt directive on the + * same line. Returns true if the right trim directive was present. + */ + private boolean deliberateRightTrim() { + boolean result = false; + for (ASTElement elem = prevTerminalNode(); + elem != null && elem.endLine == beginLine; + elem = elem.prevTerminalNode()) { + if (elem instanceof ASTDirTOrTrOrTl) { + ASTDirTOrTrOrTl ti = (ASTDirTOrTrOrTl) elem; + if (!ti.left && !ti.right) { + result = true; + } + if (ti.right) { + result = true; + int firstLineIndex = firstNewLineIndex() + 1; + if (firstLineIndex == 0) { + return false; + } + if (text.length > firstLineIndex + && text[firstLineIndex - 1] == '\r' + && text[firstLineIndex] == '\n') { + firstLineIndex++; + } + char[] trailingPart = substring(text, firstLineIndex); + char[] openingPart = substring(text, 0, firstLineIndex); + if (_StringUtil.isTrimmableToEmpty(openingPart)) { + text = trailingPart; + beginLine++; + beginColumn = 1; + } else { + int lastNonWS = openingPart.length - 1; + while (Character.isWhitespace(text[lastNonWS])) { + lastNonWS--; + } + char[] printablePart = substring(text, 0, lastNonWS + 1); + if (_StringUtil.isTrimmableToEmpty(trailingPart)) { + // THIS BLOCK IS HEINOUS! THERE MUST BE A BETTER WAY! REVISIT (JR) + boolean trimTrailingPart = true; + for (ASTElement te = nextTerminalNode(); + te != null && te.beginLine == endLine; + te = te.nextTerminalNode()) { + if (te.heedsOpeningWhitespace()) { + trimTrailingPart = false; + } + if (te instanceof ASTDirTOrTrOrTl && ((ASTDirTOrTrOrTl) te).left) { + trimTrailingPart = true; + break; + } + } + if (trimTrailingPart) trailingPart = _CollectionUtil.EMPTY_CHAR_ARRAY; + } + text = concat(printablePart, trailingPart); + } + } + } + } + return result; + } + + private int firstNewLineIndex() { + char[] text = this.text; + for (int i = 0; i < text.length; i++) { + char c = text[i]; + if (c == '\r' || c == '\n' ) { + return i; + } + } + return -1; + } + + private int lastNewLineIndex() { + char[] text = this.text; + for (int i = text.length - 1; i >= 0; i--) { + char c = text[i]; + if (c == '\r' || c == '\n' ) { + return i; + } + } + return -1; + } + + /** + * figures out how many opening whitespace characters to strip + * in the post-parse cleanup phase. + */ + private int openingCharsToStrip() { + int newlineIndex = firstNewLineIndex(); + if (newlineIndex == -1 && beginColumn != 1) { + return 0; + } + ++newlineIndex; + if (text.length > newlineIndex) { + if (newlineIndex > 0 && text[newlineIndex - 1] == '\r' && text[newlineIndex] == '\n') { + ++newlineIndex; + } + } + if (!_StringUtil.isTrimmableToEmpty(text, 0, newlineIndex)) { + return 0; + } + // We look at the preceding elements on the line to see if we should + // strip the opening newline and any whitespace preceding it. + for (ASTElement elem = prevTerminalNode(); + elem != null && elem.endLine == beginLine; + elem = elem.prevTerminalNode()) { + if (elem.heedsOpeningWhitespace()) { + return 0; + } + } + return newlineIndex; + } + + /** + * figures out how many trailing whitespace characters to strip + * in the post-parse cleanup phase. + */ + private int trailingCharsToStrip() { + int lastNewlineIndex = lastNewLineIndex(); + if (lastNewlineIndex == -1 && beginColumn != 1) { + return 0; + } + if (!_StringUtil.isTrimmableToEmpty(text, lastNewlineIndex + 1)) { + return 0; + } + // We look at the elements afterward on the same line to see if we should + // strip any whitespace after the last newline + for (ASTElement elem = nextTerminalNode(); + elem != null && elem.beginLine == endLine; + elem = elem.nextTerminalNode()) { + if (elem.heedsTrailingWhitespace()) { + return 0; + } + } + return text.length - (lastNewlineIndex + 1); + } + + @Override + boolean heedsTrailingWhitespace() { + if (isIgnorable(true)) { + return false; + } + for (char c : text) { + if (c == '\n' || c == '\r') { + return false; + } + if (!Character.isWhitespace(c)) { + return true; + } + } + return true; + } + + @Override + boolean heedsOpeningWhitespace() { + if (isIgnorable(true)) { + return false; + } + for (int i = text.length - 1; i >= 0; i--) { + char c = text[i]; + if (c == '\n' || c == '\r') { + return false; + } + if (!Character.isWhitespace(c)) { + return true; + } + } + return true; + } + + @Override + boolean isIgnorable(boolean stripWhitespace) { + if (text == null || text.length == 0) { + return true; + } + if (stripWhitespace) { + if (!_StringUtil.isTrimmableToEmpty(text)) { + return false; + } + ASTElement parentElement = getParent(); + boolean atTopLevel = isTopLevelTextIfParentIs(parentElement); + ASTElement prevSibling = previousSibling(); + ASTElement nextSibling = nextSibling(); + return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling)) + && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling)); + } else { + return false; + } + } + + private boolean isTopLevelTextIfParentIs(ASTElement parentElement) { + return parentElement == null + || parentElement.getParent() == null && parentElement instanceof ASTImplicitParent; + } + + + private boolean nonOutputtingType(ASTElement element) { + return (element instanceof ASTDirMacro || + element instanceof ASTDirAssignment || + element instanceof ASTDirAssignmentsContainer || + element instanceof ASTDirSetting || + element instanceof ASTDirImport || + element instanceof ASTComment); + } + + private static char[] substring(char[] c, int from, int to) { + char[] c2 = new char[to - from]; + System.arraycopy(c, from, c2, 0, c2.length); + return c2; + } + + private static char[] substring(char[] c, int from) { + return substring(c, from, c.length); + } + + private static char[] concat(char[] c1, char[] c2) { + char[] c = new char[c1.length + c2.length]; + System.arraycopy(c1, 0, c, 0, c1.length); + System.arraycopy(c2, 0, c, c1.length, c2.length); + return c; + } + + @Override + boolean isOutputCacheable() { + return true; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java new file mode 100644 index 0000000..764ec8a --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java @@ -0,0 +1,129 @@ +/* + * 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.arithmetic.ArithmeticEngine; +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 { + 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java new file mode 100644 index 0000000..05efa98 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInBannedWhenAutoEscaping.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInBannedWhenAutoEscaping.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInBannedWhenAutoEscaping.java new file mode 100644 index 0000000..642b939 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForDate.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForDate.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForDate.java new file mode 100644 index 0000000..33971f5 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForHashEx.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForHashEx.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForHashEx.java new file mode 100644 index 0000000..ec21061 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForLegacyEscaping.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForLegacyEscaping.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForLegacyEscaping.java new file mode 100644 index 0000000..8cdcbf6 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForLegacyEscaping.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.model.TemplateMarkupOutputModel; +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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForLoopVariable.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForLoopVariable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForLoopVariable.java new file mode 100644 index 0000000..5b7d9c3 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForMarkupOutput.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForMarkupOutput.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForMarkupOutput.java new file mode 100644 index 0000000..fa617c2 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForMarkupOutput.java @@ -0,0 +1,40 @@ +/* + * 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.TemplateMarkupOutputModel; +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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNode.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNode.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNode.java new file mode 100644 index 0000000..ca0cd61 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNodeEx.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNodeEx.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNodeEx.java new file mode 100644 index 0000000..8360cbd --- /dev/null +++ b/freemarker-core/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; + +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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNumber.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNumber.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForNumber.java new file mode 100644 index 0000000..02954f0 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java new file mode 100644 index 0000000..8c36823 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForString.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForString.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForString.java new file mode 100644 index 0000000..1102169 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java new file mode 100644 index 0000000..d2fa8be --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInWithParseTimeParameters.java @@ -0,0 +1,109 @@ +/* + * 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; + + +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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForDates.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForDates.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForDates.java new file mode 100644 index 0000000..ad11b37 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForExistenceHandling.java new file mode 100644 index 0000000..6e7cce0 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java new file mode 100644 index 0000000..8baa9cc --- /dev/null +++ b/freemarker-core/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() { } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForLoopVariables.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForLoopVariables.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForLoopVariables.java new file mode 100644 index 0000000..8d55e0e --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForLoopVariables.java @@ -0,0 +1,156 @@ +/* + * 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.ASTDirList.IterationContext; +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; +import org.apache.freemarker.core.model.impl.SimpleNumber; +import org.apache.freemarker.core.model.impl.SimpleScalar; + + +class BuiltInsForLoopVariables { + + static class indexBI extends BuiltInForLoopVariable { + + @Override + TemplateModel calculateResult(IterationContext iterCtx, Environment env) throws TemplateException { + return new SimpleNumber(iterCtx.getIndex()); + } + + } + + static class counterBI extends BuiltInForLoopVariable { + + @Override + TemplateModel calculateResult(IterationContext iterCtx, Environment env) throws TemplateException { + return new SimpleNumber(iterCtx.getIndex() + 1); + } + + } + + static abstract class BooleanBuiltInForLoopVariable extends BuiltInForLoopVariable { + + @Override + final TemplateModel calculateResult(IterationContext iterCtx, Environment env) throws TemplateException { + return calculateBooleanResult(iterCtx, env) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + } + + protected abstract boolean calculateBooleanResult(IterationContext iterCtx, Environment env); + + } + + static class has_nextBI extends BooleanBuiltInForLoopVariable { + + @Override + protected boolean calculateBooleanResult(IterationContext iterCtx, Environment env) { + return iterCtx.hasNext(); + } + + } + + static class is_lastBI extends BooleanBuiltInForLoopVariable { + + @Override + protected boolean calculateBooleanResult(IterationContext iterCtx, Environment env) { + return !iterCtx.hasNext(); + } + + } + + static class is_firstBI extends BooleanBuiltInForLoopVariable { + + @Override + protected boolean calculateBooleanResult(IterationContext iterCtx, Environment env) { + return iterCtx.getIndex() == 0; + } + + } + + static class is_odd_itemBI extends BooleanBuiltInForLoopVariable { + + @Override + protected boolean calculateBooleanResult(IterationContext iterCtx, Environment env) { + return iterCtx.getIndex() % 2 == 0; + } + + } + + static class is_even_itemBI extends BooleanBuiltInForLoopVariable { + + @Override + protected boolean calculateBooleanResult(IterationContext iterCtx, Environment env) { + return iterCtx.getIndex() % 2 != 0; + } + + } + + static class item_parityBI extends BuiltInForLoopVariable { + + private static final SimpleScalar ODD = new SimpleScalar("odd"); + private static final SimpleScalar EVEN = new SimpleScalar("even"); + + @Override + TemplateModel calculateResult(IterationContext iterCtx, Environment env) throws TemplateException { + return iterCtx.getIndex() % 2 == 0 ? ODD: EVEN; + } + + } + + static class item_parity_capBI extends BuiltInForLoopVariable { + + private static final SimpleScalar ODD = new SimpleScalar("Odd"); + private static final SimpleScalar EVEN = new SimpleScalar("Even"); + + @Override + TemplateModel calculateResult(IterationContext iterCtx, Environment env) throws TemplateException { + return iterCtx.getIndex() % 2 == 0 ? ODD: EVEN; + } + + } + + static class item_cycleBI extends BuiltInForLoopVariable { + + private class BIMethod implements TemplateMethodModelEx { + + private final IterationContext iterCtx; + + private BIMethod(IterationContext iterCtx) { + this.iterCtx = iterCtx; + } + + @Override + public Object exec(List args) throws TemplateModelException { + checkMethodArgCount(args, 1, Integer.MAX_VALUE); + return args.get(iterCtx.getIndex() % args.size()); + } + } + + @Override + TemplateModel calculateResult(IterationContext iterCtx, Environment env) throws TemplateException { + return new BIMethod(iterCtx); + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMarkupOutputs.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMarkupOutputs.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMarkupOutputs.java new file mode 100644 index 0000000..f895526 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMarkupOutputs.java @@ -0,0 +1,41 @@ +/* + * 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.TemplateMarkupOutputModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.impl.SimpleScalar; + +/** + * A holder for builtins that operate exclusively on markup output left-hand value. + */ +class BuiltInsForMarkupOutputs { + + static class markup_stringBI extends BuiltInForMarkupOutput { + + @Override + protected TemplateModel calculateResult(TemplateMarkupOutputModel model) throws TemplateModelException { + return new SimpleScalar(model.getOutputFormat().getMarkupString(model)); + } + + } + +}
