http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/Assignment.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/Assignment.java b/src/main/java/org/apache/freemarker/core/ast/Assignment.java new file mode 100644 index 0000000..e861a8e --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/Assignment.java @@ -0,0 +1,279 @@ +/* + * 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.ast; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.ast.FMParserConstants; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateNumberModel; + +/** + * An instruction that makes a single assignment, like [#local x=1]. + * This is also used as the child of {@link AssignmentInstruction}, if there are multiple assignments in the same tag, + * like in [#local x=1 x=2]. + */ +final class Assignment extends TemplateElement { + + // These must not clash with ArithmeticExpression.TYPE_... constants: + private static final int OPERATOR_TYPE_EQUALS = 0x10000; + private static final int OPERATOR_TYPE_PLUS_EQUALS = 0x10001; + private static final int OPERATOR_TYPE_PLUS_PLUS = 0x10002; + private static final int OPERATOR_TYPE_MINUS_MINUS = 0x10003; + + private final int/*enum*/ scope; + private final String variableName; + private final int operatorType; + private final Expression valueExp; + private Expression namespaceExp; + + static final int NAMESPACE = 1; + static final int LOCAL = 2; + static final int GLOBAL = 3; + + private static final Number ONE = Integer.valueOf(1); + + /** + * @param variableName the variable name to assign to. + * @param valueExp the expression to assign. + * @param scope the scope of the assignment, one of NAMESPACE, LOCAL, or GLOBAL + */ + Assignment(String variableName, + int operator, + Expression valueExp, + int scope) { + this.scope = scope; + + this.variableName = variableName; + + if (operator == FMParserConstants.EQUALS) { + operatorType = OPERATOR_TYPE_EQUALS; + } else { + switch (operator) { + case FMParserConstants.PLUS_PLUS: + operatorType = OPERATOR_TYPE_PLUS_PLUS; + break; + case FMParserConstants.MINUS_MINUS: + operatorType = OPERATOR_TYPE_MINUS_MINUS; + break; + case FMParserConstants.PLUS_EQUALS: + operatorType = OPERATOR_TYPE_PLUS_EQUALS; + break; + case FMParserConstants.MINUS_EQUALS: + operatorType = ArithmeticExpression.TYPE_SUBSTRACTION; + break; + case FMParserConstants.TIMES_EQUALS: + operatorType = ArithmeticExpression.TYPE_MULTIPLICATION; + break; + case FMParserConstants.DIV_EQUALS: + operatorType = ArithmeticExpression.TYPE_DIVISION; + break; + case FMParserConstants.MOD_EQUALS: + operatorType = ArithmeticExpression.TYPE_MODULO; + break; + default: + throw new BugException(); + } + } + + this.valueExp = valueExp; + } + + void setNamespaceExp(Expression namespaceExp) { + if (scope != NAMESPACE && namespaceExp != null) throw new BugException(); + this.namespaceExp = namespaceExp; + } + + @Override + TemplateElement[] accept(Environment env) throws TemplateException { + final Environment.Namespace namespace; + if (namespaceExp == null) { + switch (scope) { + case LOCAL: + namespace = null; + break; + case GLOBAL: + namespace = env.getGlobalNamespace(); + break; + case NAMESPACE: + namespace = env.getCurrentNamespace(); + break; + default: + throw new BugException("Unexpected scope type: " + scope); + } + } else { + TemplateModel namespaceTM = namespaceExp.eval(env); + try { + namespace = (Environment.Namespace) namespaceTM; + } catch (ClassCastException e) { + throw new NonNamespaceException(namespaceExp, namespaceTM, env); + } + if (namespace == null) { + throw InvalidReferenceException.getInstance(namespaceExp, env); + } + } + + TemplateModel value; + if (operatorType == OPERATOR_TYPE_EQUALS) { + value = valueExp.eval(env); + valueExp.assertNonNull(value, env); + } else { + TemplateModel lhoValue; + if (namespace == null) { + lhoValue = env.getLocalVariable(variableName); + } else { + lhoValue = namespace.get(variableName); + } + + if (operatorType == OPERATOR_TYPE_PLUS_EQUALS) { // Add or concat operation + if (lhoValue == null) { + throw InvalidReferenceException.getInstance( + variableName, getOperatorTypeAsString(), env); + } + + value = valueExp.eval(env); + valueExp.assertNonNull(value, env); + value = AddConcatExpression._eval(env, namespaceExp, null, lhoValue, valueExp, value); + } else { // Numerical operation + Number lhoNumber; + if (lhoValue instanceof TemplateNumberModel) { + lhoNumber = EvalUtil.modelToNumber((TemplateNumberModel) lhoValue, null); + } else if (lhoValue == null) { + throw InvalidReferenceException.getInstance(variableName, getOperatorTypeAsString(), env); + } else { + throw new NonNumericalException(variableName, lhoValue, null, env); + } + + if (operatorType == OPERATOR_TYPE_PLUS_PLUS) { + value = AddConcatExpression._evalOnNumbers(env, getParentElement(), lhoNumber, ONE); + } else if (operatorType == OPERATOR_TYPE_MINUS_MINUS) { + value = ArithmeticExpression._eval( + env, getParentElement(), lhoNumber, ArithmeticExpression.TYPE_SUBSTRACTION, ONE); + } else { // operatorType == ArithmeticExpression.TYPE_... + Number rhoNumber = valueExp.evalToNumber(env); + value = ArithmeticExpression._eval(env, this, lhoNumber, operatorType, rhoNumber); + } + } + } + + if (namespace == null) { + env.setLocalVariable(variableName, value); + } else { + namespace.put(variableName, value); + } + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder buf = new StringBuilder(); + String dn = getParentElement() instanceof AssignmentInstruction ? null : getNodeTypeSymbol(); + if (dn != null) { + if (canonical) buf.append("<"); + buf.append(dn); + buf.append(' '); + } + + buf.append(_CoreStringUtils.toFTLTopLevelTragetIdentifier(variableName)); + + if (valueExp != null) { + buf.append(' '); + } + buf.append(getOperatorTypeAsString()); + if (valueExp != null) { + buf.append(' '); + buf.append(valueExp.getCanonicalForm()); + } + if (dn != null) { + if (namespaceExp != null) { + buf.append(" in "); + buf.append(namespaceExp.getCanonicalForm()); + } + if (canonical) buf.append(">"); + } + String result = buf.toString(); + return result; + } + + @Override + String getNodeTypeSymbol() { + return getDirectiveName(scope); + } + + static String getDirectiveName(int scope) { + if (scope == Assignment.LOCAL) { + return "#local"; + } else if (scope == Assignment.GLOBAL) { + return "#global"; + } else if (scope == Assignment.NAMESPACE) { + return "#assign"; + } else { + return "#{unknown_assignment_type}"; + } + } + + @Override + int getParameterCount() { + return 5; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return variableName; + case 1: return getOperatorTypeAsString(); + case 2: return valueExp; + case 3: return Integer.valueOf(scope); + case 4: return namespaceExp; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + switch (idx) { + case 0: return ParameterRole.ASSIGNMENT_TARGET; + case 1: return ParameterRole.ASSIGNMENT_OPERATOR; + case 2: return ParameterRole.ASSIGNMENT_SOURCE; + case 3: return ParameterRole.VARIABLE_SCOPE; + case 4: return ParameterRole.NAMESPACE; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + + private String getOperatorTypeAsString() { + if (operatorType == OPERATOR_TYPE_EQUALS) { + return "="; + } else if (operatorType == OPERATOR_TYPE_PLUS_EQUALS) { + return "+="; + } else if (operatorType == OPERATOR_TYPE_PLUS_PLUS) { + return "++"; + } else if (operatorType == OPERATOR_TYPE_MINUS_MINUS) { + return "--"; + } else { + return ArithmeticExpression.getOperatorSymbol(operatorType) + "="; + } + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/AssignmentInstruction.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/AssignmentInstruction.java b/src/main/java/org/apache/freemarker/core/ast/AssignmentInstruction.java new file mode 100644 index 0000000..caff0bb --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/AssignmentInstruction.java @@ -0,0 +1,117 @@ +/* + * 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.ast; + +import java.io.IOException; + +import org.apache.freemarker.core.TemplateException; + +/** + * An instruction that does multiple assignments, like [#local x=1 x=2]. + * Each assignment is represented by a {@link Assignment} child element. + * If there's only one assignment, its usually just a {@link Assignment} without parent {@link AssignmentInstruction}. + */ +final class AssignmentInstruction extends TemplateElement { + + private int scope; + private Expression namespaceExp; + + AssignmentInstruction(int scope) { + this.scope = scope; + setChildBufferCapacity(1); + } + + void addAssignment(Assignment assignment) { + addChild(assignment); + } + + void setNamespaceExp(Expression namespaceExp) { + this.namespaceExp = namespaceExp; + int ln = getChildCount(); + for (int i = 0; i < ln; i++) { + ((Assignment) getChild(i)).setNamespaceExp(namespaceExp); + } + } + + @Override + TemplateElement[] accept(Environment env) throws TemplateException, IOException { + return getChildBuffer(); + } + + @Override + protected String dump(boolean canonical) { + StringBuilder buf = new StringBuilder(); + if (canonical) buf.append('<'); + buf.append(Assignment.getDirectiveName(scope)); + if (canonical) { + buf.append(' '); + int ln = getChildCount(); + for (int i = 0; i < ln; i++) { + if (i != 0) { + buf.append(", "); + } + Assignment assignment = (Assignment) getChild(i); + buf.append(assignment.getCanonicalForm()); + } + } else { + buf.append("-container"); + } + if (namespaceExp != null) { + buf.append(" in "); + buf.append(namespaceExp.getCanonicalForm()); + } + if (canonical) buf.append(">"); + return buf.toString(); + } + + @Override + int getParameterCount() { + return 2; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return Integer.valueOf(scope); + case 1: return namespaceExp; + default: return null; + } + } + + @Override + ParameterRole getParameterRole(int idx) { + switch (idx) { + case 0: return ParameterRole.VARIABLE_SCOPE; + case 1: return ParameterRole.NAMESPACE; + default: return null; + } + } + + @Override + String getNodeTypeSymbol() { + return Assignment.getDirectiveName(scope); + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/AttemptBlock.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/AttemptBlock.java b/src/main/java/org/apache/freemarker/core/ast/AttemptBlock.java new file mode 100644 index 0000000..ee39820 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/AttemptBlock.java @@ -0,0 +1,89 @@ +/* + * 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.ast; + +import java.io.IOException; + +import org.apache.freemarker.core.TemplateException; + +/** + * Holder for the attempted section of the #attempt element and of the nested #recover element ({@link RecoveryBlock}). + */ +final class AttemptBlock extends TemplateElement { + + private TemplateElement attemptedSection; + private RecoveryBlock recoverySection; + + AttemptBlock(TemplateElements attemptedSectionChildren, RecoveryBlock recoverySection) { + TemplateElement attemptedSection = attemptedSectionChildren.asSingleElement(); + this.attemptedSection = attemptedSection; + this.recoverySection = recoverySection; + setChildBufferCapacity(2); + addChild(attemptedSection); // for backward compatibility + addChild(recoverySection); + } + + @Override + TemplateElement[] accept(Environment env) throws TemplateException, IOException { + env.visitAttemptRecover(this, attemptedSection, recoverySection); + return null; + } + + @Override + protected String dump(boolean canonical) { + if (!canonical) { + return getNodeTypeSymbol(); + } else { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getNodeTypeSymbol()).append(">"); + buf.append(getChildrenCanonicalForm()); + buf.append("</").append(getNodeTypeSymbol()).append(">"); + return buf.toString(); + } + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return recoverySection; + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return ParameterRole.ERROR_HANDLER; + } + + @Override + String getNodeTypeSymbol() { + return "#attempt"; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/AutoEscBlock.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/AutoEscBlock.java b/src/main/java/org/apache/freemarker/core/ast/AutoEscBlock.java new file mode 100644 index 0000000..beb0bee --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/AutoEscBlock.java @@ -0,0 +1,79 @@ +/* + * 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.ast; + +import java.io.IOException; + +import org.apache.freemarker.core.TemplateException; + +/** + * An #autoEsc element + */ +final class AutoEscBlock extends TemplateElement { + + AutoEscBlock(TemplateElements children) { + setChildren(children); + } + + @Override + TemplateElement[] accept(Environment env) throws TemplateException, IOException { + return getChildBuffer(); + } + + @Override + protected String dump(boolean canonical) { + if (canonical) { + return "<" + getNodeTypeSymbol() + "\">" + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">"; + } else { + return getNodeTypeSymbol(); + } + } + + @Override + String getNodeTypeSymbol() { + return "#autoesc"; + } + + @Override + int getParameterCount() { + return 0; + } + + @Override + Object getParameterValue(int idx) { + throw new IndexOutOfBoundsException(); + } + + @Override + ParameterRole getParameterRole(int idx) { + throw new IndexOutOfBoundsException(); + } + + @Override + boolean isIgnorable(boolean stripWhitespace) { + return getChildCount() == 0; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BackwardCompatibleTemplateNumberFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BackwardCompatibleTemplateNumberFormat.java b/src/main/java/org/apache/freemarker/core/ast/BackwardCompatibleTemplateNumberFormat.java new file mode 100644 index 0000000..448dc26 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BackwardCompatibleTemplateNumberFormat.java @@ -0,0 +1,30 @@ +/* + * 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.ast; + +/** + * Only exists for emulating pre-2.3.24-IcI {@code ?string} behavior. + * + * @since 2.3.24 + */ +abstract class BackwardCompatibleTemplateNumberFormat extends TemplateNumberFormat { + + abstract String format(Number number) throws UnformattableValueException; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BlockAssignment.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BlockAssignment.java b/src/main/java/org/apache/freemarker/core/ast/BlockAssignment.java new file mode 100644 index 0000000..effff2e --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BlockAssignment.java @@ -0,0 +1,183 @@ +/* + * 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.ast; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Map; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateTransformModel; +import org.apache.freemarker.core.model.impl.SimpleScalar; + +/** + * Like [#local x]...[/#local]. + */ +final class BlockAssignment extends TemplateElement { + + private final String varName; + private final Expression namespaceExp; + private final int scope; + private final MarkupOutputFormat<?> markupOutputFormat; + + BlockAssignment(TemplateElements children, String varName, int scope, Expression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) { + setChildren(children); + this.varName = varName; + this.namespaceExp = namespaceExp; + this.scope = scope; + this.markupOutputFormat = markupOutputFormat; + } + + @Override + TemplateElement[] accept(Environment env) throws TemplateException, IOException { + TemplateElement[] children = getChildBuffer(); + if (children != null) { + env.visitAndTransform(children, new CaptureOutput(env), null); + } else { + TemplateModel value = capturedStringToModel(""); + if (namespaceExp != null) { + Environment.Namespace ns = (Environment.Namespace) namespaceExp.eval(env); + ns.put(varName, value); + } else if (scope == Assignment.NAMESPACE) { + env.setVariable(varName, value); + } else if (scope == Assignment.GLOBAL) { + env.setGlobalVariable(varName, value); + } else if (scope == Assignment.LOCAL) { + env.setLocalVariable(varName, value); + } + } + return null; + } + + private TemplateModel capturedStringToModel(String s) throws TemplateModelException { + return markupOutputFormat == null ? new SimpleScalar(s) : markupOutputFormat.fromMarkup(s); + } + + private class CaptureOutput implements TemplateTransformModel { + private final Environment env; + private final Environment.Namespace fnsModel; + + CaptureOutput(Environment env) throws TemplateException { + this.env = env; + TemplateModel nsModel = null; + if (namespaceExp != null) { + nsModel = namespaceExp.eval(env); + if (!(nsModel instanceof Environment.Namespace)) { + throw new NonNamespaceException(namespaceExp, nsModel, env); + } + } + fnsModel = (Environment.Namespace ) nsModel; + } + + public Writer getWriter(Writer out, Map args) { + return new StringWriter() { + @Override + public void close() throws IOException { + TemplateModel result; + try { + result = capturedStringToModel(toString()); + } catch (TemplateModelException e) { + // [Java 1.6] e to cause + throw new IOException("Failed to create FTL value from captured string: " + e); + } + switch(scope) { + case Assignment.NAMESPACE: { + if (fnsModel != null) { + fnsModel.put(varName, result); + } else { + env.setVariable(varName, result); + } + break; + } + case Assignment.LOCAL: { + env.setLocalVariable(varName, result); + break; + } + case Assignment.GLOBAL: { + env.setGlobalVariable(varName, result); + break; + } + } + } + }; + } + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append("<"); + sb.append(getNodeTypeSymbol()); + sb.append(' '); + sb.append(varName); + if (namespaceExp != null) { + sb.append(" in "); + sb.append(namespaceExp.getCanonicalForm()); + } + if (canonical) { + sb.append('>'); + sb.append(getChildrenCanonicalForm()); + sb.append("</"); + sb.append(getNodeTypeSymbol()); + sb.append('>'); + } else { + sb.append(" = .nested_output"); + } + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return Assignment.getDirectiveName(scope); + } + + @Override + int getParameterCount() { + return 3; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return varName; + case 1: return Integer.valueOf(scope); + case 2: return namespaceExp; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + switch (idx) { + case 0: return ParameterRole.ASSIGNMENT_TARGET; + case 1: return ParameterRole.VARIABLE_SCOPE; + case 2: return ParameterRole.NAMESPACE; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BodyInstruction.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BodyInstruction.java b/src/main/java/org/apache/freemarker/core/ast/BodyInstruction.java new file mode 100644 index 0000000..bcdac8b --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BodyInstruction.java @@ -0,0 +1,158 @@ +/* + * 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.ast; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * An instruction that processes the nested block within a macro instruction. + */ +final class BodyInstruction extends TemplateElement { + + + private List bodyParameters; + + + BodyInstruction(List bodyParameters) { + this.bodyParameters = bodyParameters; + } + + List getBodyParameters() { + return bodyParameters; + } + + /** + * There is actually a subtle but essential point in the code below. + * A macro operates in the context in which it's defined. However, + * a nested block within a macro instruction is defined in the + * context in which the macro was invoked. So, we actually need to + * temporarily switch the namespace and macro context back to + * what it was before macro invocation to implement this properly. + * I (JR) realized this thanks to some incisive comments from Daniel Dekany. + */ + @Override + TemplateElement[] accept(Environment env) throws IOException, TemplateException { + Context bodyContext = new Context(env); + env.invokeNestedContent(bodyContext); + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + if (bodyParameters != null) { + for (int i = 0; i < bodyParameters.size(); i++) { + sb.append(' '); + sb.append(((Expression) bodyParameters.get(i)).getCanonicalForm()); + } + } + if (canonical) sb.append('>'); + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#nested"; + } + + @Override + int getParameterCount() { + return bodyParameters != null ? bodyParameters.size() : 0; + } + + @Override + Object getParameterValue(int idx) { + checkIndex(idx); + return bodyParameters.get(idx); + } + + @Override + ParameterRole getParameterRole(int idx) { + checkIndex(idx); + return ParameterRole.PASSED_VALUE; + } + + private void checkIndex(int idx) { + if (bodyParameters == null || idx >= bodyParameters.size()) { + throw new IndexOutOfBoundsException(); + } + } + + /* + boolean heedsOpeningWhitespace() { + return true; + } + + boolean heedsTrailingWhitespace() { + return true; + } + */ + + @Override + boolean isShownInStackTrace() { + return true; + } + + class Context implements LocalContext { + Macro.Context invokingMacroContext; + Environment.Namespace bodyVars; + + Context(Environment env) throws TemplateException { + invokingMacroContext = env.getCurrentMacroContext(); + List bodyParameterNames = invokingMacroContext.nestedContentParameterNames; + if (bodyParameters != null) { + for (int i = 0; i < bodyParameters.size(); i++) { + Expression exp = (Expression) bodyParameters.get(i); + TemplateModel tm = exp.eval(env); + if (bodyParameterNames != null && i < bodyParameterNames.size()) { + String bodyParameterName = (String) bodyParameterNames.get(i); + if (bodyVars == null) { + bodyVars = env.new Namespace(); + } + bodyVars.put(bodyParameterName, tm); + } + } + } + } + + public TemplateModel getLocalVariable(String name) throws TemplateModelException { + return bodyVars == null ? null : bodyVars.get(name); + } + + public Collection getLocalVariableNames() { + List bodyParameterNames = invokingMacroContext.nestedContentParameterNames; + return bodyParameterNames == null ? Collections.EMPTY_LIST : bodyParameterNames; + } + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BooleanExpression.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BooleanExpression.java b/src/main/java/org/apache/freemarker/core/ast/BooleanExpression.java new file mode 100644 index 0000000..08e1dc7 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BooleanExpression.java @@ -0,0 +1,32 @@ +/* + * 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.ast; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateModel; + +abstract class BooleanExpression extends Expression { + + @Override + TemplateModel _eval(Environment env) throws TemplateException { + return evalToBoolean(env) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BooleanLiteral.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BooleanLiteral.java b/src/main/java/org/apache/freemarker/core/ast/BooleanLiteral.java new file mode 100644 index 0000000..82cfb8f --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BooleanLiteral.java @@ -0,0 +1,88 @@ +/* + * 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.ast; + +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateModel; + +final class BooleanLiteral extends Expression { + + private final boolean val; + + public BooleanLiteral(boolean val) { + this.val = val; + } + + static TemplateBooleanModel getTemplateModel(boolean b) { + return b? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + } + + @Override + boolean evalToBoolean(Environment env) { + return val; + } + + @Override + public String getCanonicalForm() { + return val ? MiscUtil.C_TRUE : MiscUtil.C_FALSE; + } + + @Override + String getNodeTypeSymbol() { + return getCanonicalForm(); + } + + @Override + public String toString() { + return val ? MiscUtil.C_TRUE : MiscUtil.C_FALSE; + } + + @Override + TemplateModel _eval(Environment env) { + return val ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + } + + @Override + boolean isLiteral() { + return true; + } + + @Override + protected Expression deepCloneWithIdentifierReplaced_inner( + String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) { + return new BooleanLiteral(val); + } + + @Override + int getParameterCount() { + return 0; + } + + @Override + Object getParameterValue(int idx) { + throw new IndexOutOfBoundsException(); + } + + @Override + ParameterRole getParameterRole(int idx) { + throw new IndexOutOfBoundsException(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BoundedRangeModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BoundedRangeModel.java b/src/main/java/org/apache/freemarker/core/ast/BoundedRangeModel.java new file mode 100644 index 0000000..65a8821 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BoundedRangeModel.java @@ -0,0 +1,69 @@ +/* + * 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.ast; + + +/** + * 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; + this.affectedByStringSlicingBug = inclusiveEnd; + } + + 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/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BreakInstruction.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BreakInstruction.java b/src/main/java/org/apache/freemarker/core/ast/BreakInstruction.java new file mode 100644 index 0000000..67a770e --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BreakInstruction.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.ast; + +/** + * Represents a <break> instruction to break out of a loop. + */ +final class BreakInstruction extends TemplateElement { + + @Override + TemplateElement[] accept(Environment env) { + throw Break.INSTANCE; + } + + @Override + protected String dump(boolean canonical) { + return canonical ? "<" + getNodeTypeSymbol() + "/>" : getNodeTypeSymbol(); + } + + @Override + String getNodeTypeSymbol() { + return "#break"; + } + + @Override + int getParameterCount() { + return 0; + } + + @Override + Object getParameterValue(int idx) { + throw new IndexOutOfBoundsException(); + } + + @Override + ParameterRole getParameterRole(int idx) { + throw new IndexOutOfBoundsException(); + } + + static class Break extends RuntimeException { + static final Break INSTANCE = new Break(); + private Break() { + } + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} + + http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BugException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BugException.java b/src/main/java/org/apache/freemarker/core/ast/BugException.java new file mode 100644 index 0000000..9850b74 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BugException.java @@ -0,0 +1,52 @@ +/* + * 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.ast; + +/** + * An unexpected state was reached that is certainly caused by a bug in FreeMarker. + * + * @since 2.3.21 + */ +public class BugException extends RuntimeException { + + private static final String COMMON_MESSAGE + = "A bug was detected in FreeMarker; please report it with stack-trace"; + + public BugException() { + this((Throwable) null); + } + + public BugException(String message) { + this(message, null); + } + + public BugException(Throwable cause) { + super(COMMON_MESSAGE, cause); + } + + public BugException(String message, Throwable cause) { + super(COMMON_MESSAGE + ": " + message, cause); + } + + public BugException(int value) { + this(String.valueOf(value)); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltIn.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltIn.java b/src/main/java/org/apache/freemarker/core/ast/BuiltIn.java new file mode 100644 index 0000000..3c8fc42 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BuiltIn.java @@ -0,0 +1,495 @@ +/* + * 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.ast; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.ast.FMParserTokenManager; +import org.apache.freemarker.core.ast.Token; +import org.apache.freemarker.core.ast.BuiltInsForDates.iso_BI; +import org.apache.freemarker.core.ast.BuiltInsForDates.iso_utc_or_local_BI; +import org.apache.freemarker.core.ast.BuiltInsForMarkupOutputs.markup_stringBI; +import org.apache.freemarker.core.ast.BuiltInsForMultipleTypes.is_dateLikeBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.ancestorsBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.childrenBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.nextSiblingBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.node_nameBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.node_namespaceBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.node_typeBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.parentBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.previousSiblingBI; +import org.apache.freemarker.core.ast.BuiltInsForNodes.rootBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.absBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.byteBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.ceilingBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.doubleBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.floatBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.floorBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.intBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.is_infiniteBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.is_nanBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.longBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.number_to_dateBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.roundBI; +import org.apache.freemarker.core.ast.BuiltInsForNumbers.shortBI; +import org.apache.freemarker.core.ast.BuiltInsForOutputFormatRelated.escBI; +import org.apache.freemarker.core.ast.BuiltInsForOutputFormatRelated.no_escBI; +import org.apache.freemarker.core.ast.BuiltInsForSequences.chunkBI; +import org.apache.freemarker.core.ast.BuiltInsForSequences.firstBI; +import org.apache.freemarker.core.ast.BuiltInsForSequences.lastBI; +import org.apache.freemarker.core.ast.BuiltInsForSequences.reverseBI; +import org.apache.freemarker.core.ast.BuiltInsForSequences.seq_containsBI; +import org.apache.freemarker.core.ast.BuiltInsForSequences.seq_index_ofBI; +import org.apache.freemarker.core.ast.BuiltInsForSequences.sortBI; +import org.apache.freemarker.core.ast.BuiltInsForSequences.sort_byBI; +import org.apache.freemarker.core.ast.BuiltInsForStringsMisc.evalBI; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.util.DateUtil; +import org.apache.freemarker.core.util.StringUtil; + +/** + * The {@code ?} operator used for things like {@code foo?upper_case}. + */ +abstract class BuiltIn extends Expression implements Cloneable { + + protected Expression target; + protected String key; + + static final Set<String> CAMEL_CASE_NAMES = new TreeSet<String>(); + static final Set<String> SNAKE_CASE_NAMES = new TreeSet<String>(); + static final int NUMBER_OF_BIS = 263; + static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f); + + static { + // Note that you must update NUMBER_OF_BIS if you add new items here! + + putBI("abs", new absBI()); + putBI("ancestors", new ancestorsBI()); + putBI("api", new BuiltInsForMultipleTypes.apiBI()); + putBI("boolean", new BuiltInsForStringsMisc.booleanBI()); + putBI("byte", new byteBI()); + putBI("c", new BuiltInsForMultipleTypes.cBI()); + putBI("cap_first", "capFirst", new BuiltInsForStringsBasic.cap_firstBI()); + putBI("capitalize", new BuiltInsForStringsBasic.capitalizeBI()); + putBI("ceiling", new ceilingBI()); + putBI("children", new childrenBI()); + putBI("chop_linebreak", "chopLinebreak", new BuiltInsForStringsBasic.chop_linebreakBI()); + putBI("contains", new BuiltInsForStringsBasic.containsBI()); + putBI("date", new BuiltInsForMultipleTypes.dateBI(TemplateDateModel.DATE)); + putBI("date_if_unknown", "dateIfUnknown", new BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.DATE)); + putBI("datetime", new BuiltInsForMultipleTypes.dateBI(TemplateDateModel.DATETIME)); + putBI("datetime_if_unknown", "datetimeIfUnknown", new BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.DATETIME)); + putBI("default", new ExistenceBuiltins.defaultBI()); + putBI("double", new doubleBI()); + putBI("ends_with", "endsWith", new BuiltInsForStringsBasic.ends_withBI()); + putBI("ensure_ends_with", "ensureEndsWith", new BuiltInsForStringsBasic.ensure_ends_withBI()); + putBI("ensure_starts_with", "ensureStartsWith", new BuiltInsForStringsBasic.ensure_starts_withBI()); + putBI("esc", new escBI()); + putBI("eval", new evalBI()); + putBI("exists", new ExistenceBuiltins.existsBI()); + putBI("first", new firstBI()); + putBI("float", new floatBI()); + putBI("floor", new floorBI()); + putBI("chunk", new chunkBI()); + putBI("counter", new BuiltInsForLoopVariables.counterBI()); + putBI("item_cycle", "itemCycle", new BuiltInsForLoopVariables.item_cycleBI()); + putBI("has_api", "hasApi", new BuiltInsForMultipleTypes.has_apiBI()); + putBI("has_content", "hasContent", new ExistenceBuiltins.has_contentBI()); + putBI("has_next", "hasNext", new BuiltInsForLoopVariables.has_nextBI()); + putBI("html", new BuiltInsForStringsEncoding.htmlBI()); + putBI("if_exists", "ifExists", new ExistenceBuiltins.if_existsBI()); + putBI("index", new BuiltInsForLoopVariables.indexBI()); + putBI("index_of", "indexOf", new BuiltInsForStringsBasic.index_ofBI(false)); + putBI("int", new intBI()); + putBI("interpret", new Interpret()); + putBI("is_boolean", "isBoolean", new BuiltInsForMultipleTypes.is_booleanBI()); + putBI("is_collection", "isCollection", new BuiltInsForMultipleTypes.is_collectionBI()); + putBI("is_collection_ex", "isCollectionEx", new BuiltInsForMultipleTypes.is_collection_exBI()); + is_dateLikeBI bi = new BuiltInsForMultipleTypes.is_dateLikeBI(); + putBI("is_date", "isDate", bi); // misnomer + putBI("is_date_like", "isDateLike", bi); + putBI("is_date_only", "isDateOnly", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.DATE)); + putBI("is_even_item", "isEvenItem", new BuiltInsForLoopVariables.is_even_itemBI()); + putBI("is_first", "isFirst", new BuiltInsForLoopVariables.is_firstBI()); + putBI("is_last", "isLast", new BuiltInsForLoopVariables.is_lastBI()); + putBI("is_unknown_date_like", "isUnknownDateLike", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.UNKNOWN)); + putBI("is_datetime", "isDatetime", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.DATETIME)); + putBI("is_directive", "isDirective", new BuiltInsForMultipleTypes.is_directiveBI()); + putBI("is_enumerable", "isEnumerable", new BuiltInsForMultipleTypes.is_enumerableBI()); + putBI("is_hash_ex", "isHashEx", new BuiltInsForMultipleTypes.is_hash_exBI()); + putBI("is_hash", "isHash", new BuiltInsForMultipleTypes.is_hashBI()); + putBI("is_infinite", "isInfinite", new is_infiniteBI()); + putBI("is_indexable", "isIndexable", new BuiltInsForMultipleTypes.is_indexableBI()); + putBI("is_macro", "isMacro", new BuiltInsForMultipleTypes.is_macroBI()); + putBI("is_markup_output", "isMarkupOutput", new BuiltInsForMultipleTypes.is_markup_outputBI()); + putBI("is_method", "isMethod", new BuiltInsForMultipleTypes.is_methodBI()); + putBI("is_nan", "isNan", new is_nanBI()); + putBI("is_node", "isNode", new BuiltInsForMultipleTypes.is_nodeBI()); + putBI("is_number", "isNumber", new BuiltInsForMultipleTypes.is_numberBI()); + putBI("is_odd_item", "isOddItem", new BuiltInsForLoopVariables.is_odd_itemBI()); + putBI("is_sequence", "isSequence", new BuiltInsForMultipleTypes.is_sequenceBI()); + putBI("is_string", "isString", new BuiltInsForMultipleTypes.is_stringBI()); + putBI("is_time", "isTime", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.TIME)); + putBI("is_transform", "isTransform", new BuiltInsForMultipleTypes.is_transformBI()); + + putBI("iso_utc", "isoUtc", new iso_utc_or_local_BI( + /* showOffset = */ null, DateUtil.ACCURACY_SECONDS, /* useUTC = */ true)); + putBI("iso_utc_fz", "isoUtcFZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.TRUE, DateUtil.ACCURACY_SECONDS, /* useUTC = */ true)); + putBI("iso_utc_nz", "isoUtcNZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_SECONDS, /* useUTC = */ true)); + + putBI("iso_utc_ms", "isoUtcMs", new iso_utc_or_local_BI( + /* showOffset = */ null, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ true)); + putBI("iso_utc_ms_nz", "isoUtcMsNZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ true)); + + putBI("iso_utc_m", "isoUtcM", new iso_utc_or_local_BI( + /* showOffset = */ null, DateUtil.ACCURACY_MINUTES, /* useUTC = */ true)); + putBI("iso_utc_m_nz", "isoUtcMNZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MINUTES, /* useUTC = */ true)); + + putBI("iso_utc_h", "isoUtcH", new iso_utc_or_local_BI( + /* showOffset = */ null, DateUtil.ACCURACY_HOURS, /* useUTC = */ true)); + putBI("iso_utc_h_nz", "isoUtcHNZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_HOURS, /* useUTC = */ true)); + + putBI("iso_local", "isoLocal", new iso_utc_or_local_BI( + /* showOffset = */ null, DateUtil.ACCURACY_SECONDS, /* useUTC = */ false)); + putBI("iso_local_nz", "isoLocalNZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_SECONDS, /* useUTC = */ false)); + + putBI("iso_local_ms", "isoLocalMs", new iso_utc_or_local_BI( + /* showOffset = */ null, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ false)); + putBI("iso_local_ms_nz", "isoLocalMsNZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ false)); + + putBI("iso_local_m", "isoLocalM", new iso_utc_or_local_BI( + /* showOffset = */ null, DateUtil.ACCURACY_MINUTES, /* useUTC = */ false)); + putBI("iso_local_m_nz", "isoLocalMNZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MINUTES, /* useUTC = */ false)); + + putBI("iso_local_h", "isoLocalH", new iso_utc_or_local_BI( + /* showOffset = */ null, DateUtil.ACCURACY_HOURS, /* useUTC = */ false)); + putBI("iso_local_h_nz", "isoLocalHNZ", new iso_utc_or_local_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_HOURS, /* useUTC = */ false)); + + putBI("iso", new iso_BI( + /* showOffset = */ null, DateUtil.ACCURACY_SECONDS)); + putBI("iso_nz", "isoNZ", new iso_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_SECONDS)); + + putBI("iso_ms", "isoMs", new iso_BI( + /* showOffset = */ null, DateUtil.ACCURACY_MILLISECONDS)); + putBI("iso_ms_nz", "isoMsNZ", new iso_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MILLISECONDS)); + + putBI("iso_m", "isoM", new iso_BI( + /* showOffset = */ null, DateUtil.ACCURACY_MINUTES)); + putBI("iso_m_nz", "isoMNZ", new iso_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_MINUTES)); + + putBI("iso_h", "isoH", new iso_BI( + /* showOffset = */ null, DateUtil.ACCURACY_HOURS)); + putBI("iso_h_nz", "isoHNZ", new iso_BI( + /* showOffset = */ Boolean.FALSE, DateUtil.ACCURACY_HOURS)); + + putBI("j_string", "jString", new BuiltInsForStringsEncoding.j_stringBI()); + putBI("join", new BuiltInsForSequences.joinBI()); + putBI("js_string", "jsString", new BuiltInsForStringsEncoding.js_stringBI()); + putBI("json_string", "jsonString", new BuiltInsForStringsEncoding.json_stringBI()); + putBI("keep_after", "keepAfter", new BuiltInsForStringsBasic.keep_afterBI()); + putBI("keep_before", "keepBefore", new BuiltInsForStringsBasic.keep_beforeBI()); + putBI("keep_after_last", "keepAfterLast", new BuiltInsForStringsBasic.keep_after_lastBI()); + putBI("keep_before_last", "keepBeforeLast", new BuiltInsForStringsBasic.keep_before_lastBI()); + putBI("keys", new BuiltInsForHashes.keysBI()); + putBI("last_index_of", "lastIndexOf", new BuiltInsForStringsBasic.index_ofBI(true)); + putBI("last", new lastBI()); + putBI("left_pad", "leftPad", new BuiltInsForStringsBasic.padBI(true)); + putBI("length", new BuiltInsForStringsBasic.lengthBI()); + putBI("long", new longBI()); + putBI("lower_abc", "lowerAbc", new BuiltInsForNumbers.lower_abcBI()); + putBI("lower_case", "lowerCase", new BuiltInsForStringsBasic.lower_caseBI()); + putBI("namespace", new BuiltInsForMultipleTypes.namespaceBI()); + putBI("new", new NewBI()); + putBI("markup_string", "markupString", new markup_stringBI()); + putBI("node_name", "nodeName", new node_nameBI()); + putBI("node_namespace", "nodeNamespace", new node_namespaceBI()); + putBI("node_type", "nodeType", new node_typeBI()); + putBI("no_esc", "noEsc", new no_escBI()); + putBI("number", new BuiltInsForStringsMisc.numberBI()); + putBI("number_to_date", "numberToDate", new number_to_dateBI(TemplateDateModel.DATE)); + putBI("number_to_time", "numberToTime", new number_to_dateBI(TemplateDateModel.TIME)); + putBI("number_to_datetime", "numberToDatetime", new number_to_dateBI(TemplateDateModel.DATETIME)); + putBI("parent", new parentBI()); + putBI("previous_sibling", "previousSibling", new previousSiblingBI()); + putBI("next_sibling", "nextSibling", new nextSiblingBI()); + putBI("item_parity", "itemParity", new BuiltInsForLoopVariables.item_parityBI()); + putBI("item_parity_cap", "itemParityCap", new BuiltInsForLoopVariables.item_parity_capBI()); + putBI("reverse", new reverseBI()); + putBI("right_pad", "rightPad", new BuiltInsForStringsBasic.padBI(false)); + putBI("root", new rootBI()); + putBI("round", new roundBI()); + putBI("remove_ending", "removeEnding", new BuiltInsForStringsBasic.remove_endingBI()); + putBI("remove_beginning", "removeBeginning", new BuiltInsForStringsBasic.remove_beginningBI()); + putBI("rtf", new BuiltInsForStringsEncoding.rtfBI()); + putBI("seq_contains", "seqContains", new seq_containsBI()); + putBI("seq_index_of", "seqIndexOf", new seq_index_ofBI(1)); + putBI("seq_last_index_of", "seqLastIndexOf", new seq_index_ofBI(-1)); + putBI("short", new shortBI()); + putBI("size", new BuiltInsForMultipleTypes.sizeBI()); + putBI("sort_by", "sortBy", new sort_byBI()); + putBI("sort", new sortBI()); + putBI("split", new BuiltInsForStringsBasic.split_BI()); + putBI("switch", new BuiltInsWithParseTimeParameters.switch_BI()); + putBI("starts_with", "startsWith", new BuiltInsForStringsBasic.starts_withBI()); + putBI("string", new BuiltInsForMultipleTypes.stringBI()); + putBI("substring", new BuiltInsForStringsBasic.substringBI()); + putBI("then", new BuiltInsWithParseTimeParameters.then_BI()); + putBI("time", new BuiltInsForMultipleTypes.dateBI(TemplateDateModel.TIME)); + putBI("time_if_unknown", "timeIfUnknown", new BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.TIME)); + putBI("trim", new BuiltInsForStringsBasic.trimBI()); + putBI("uncap_first", "uncapFirst", new BuiltInsForStringsBasic.uncap_firstBI()); + putBI("upper_abc", "upperAbc", new BuiltInsForNumbers.upper_abcBI()); + putBI("upper_case", "upperCase", new BuiltInsForStringsBasic.upper_caseBI()); + putBI("url", new BuiltInsForStringsEncoding.urlBI()); + putBI("url_path", "urlPath", new BuiltInsForStringsEncoding.urlPathBI()); + putBI("values", new BuiltInsForHashes.valuesBI()); + putBI("web_safe", "webSafe", BUILT_INS_BY_NAME.get("html")); // deprecated; use ?html instead + putBI("word_list", "wordList", new BuiltInsForStringsBasic.word_listBI()); + putBI("xhtml", new BuiltInsForStringsEncoding.xhtmlBI()); + putBI("xml", new BuiltInsForStringsEncoding.xmlBI()); + putBI("matches", new BuiltInsForStringsRegexp.matchesBI()); + putBI("groups", new BuiltInsForStringsRegexp.groupsBI()); + putBI("replace", new BuiltInsForStringsRegexp.replace_reBI()); + + + if (NUMBER_OF_BIS < BUILT_INS_BY_NAME.size()) { + throw new AssertionError("Update NUMBER_OF_BIS! Should be: " + BUILT_INS_BY_NAME.size()); + } + } + + private static void putBI(String name, BuiltIn bi) { + BUILT_INS_BY_NAME.put(name, bi); + SNAKE_CASE_NAMES.add(name); + CAMEL_CASE_NAMES.add(name); + } + + private static void putBI(String nameSnakeCase, String nameCamelCase, BuiltIn bi) { + BUILT_INS_BY_NAME.put(nameSnakeCase, bi); + BUILT_INS_BY_NAME.put(nameCamelCase, bi); + SNAKE_CASE_NAMES.add(nameSnakeCase); + CAMEL_CASE_NAMES.add(nameCamelCase); + } + + /** + * @param target + * Left-hand-operand expression + * @param keyTk + * Built-in name token + */ + static BuiltIn newBuiltIn(int incompatibleImprovements, Expression target, Token keyTk, + FMParserTokenManager tokenManager) throws ParseException { + String key = keyTk.image; + BuiltIn bi = BUILT_INS_BY_NAME.get(key); + if (bi == null) { + StringBuilder buf = new StringBuilder("Unknown built-in: ").append(StringUtil.jQuote(key)).append(". "); + + buf.append( + "Help (latest version): http://freemarker.org/docs/ref_builtins.html; " + + "you're using FreeMarker ").append(Configuration.getVersion()).append(".\n" + + "The alphabetical list of built-ins:"); + List names = new ArrayList(BUILT_INS_BY_NAME.keySet().size()); + names.addAll(BUILT_INS_BY_NAME.keySet()); + Collections.sort(names); + char lastLetter = 0; + + int shownNamingConvention; + { + int namingConvention = tokenManager.namingConvention; + shownNamingConvention = namingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION + ? namingConvention : Configuration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */; + } + + boolean first = true; + for (Iterator it = names.iterator(); it.hasNext(); ) { + String correctName = (String) it.next(); + int correctNameNamingConvetion = _CoreStringUtils.getIdentifierNamingConvention(correctName); + if (shownNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION + ? correctNameNamingConvetion != Configuration.LEGACY_NAMING_CONVENTION + : correctNameNamingConvetion != Configuration.CAMEL_CASE_NAMING_CONVENTION) { + if (first) { + first = false; + } else { + buf.append(", "); + } + + char firstChar = correctName.charAt(0); + if (firstChar != lastLetter) { + lastLetter = firstChar; + buf.append('\n'); + } + buf.append(correctName); + } + } + + throw new ParseException(buf.toString(), null, keyTk); + } + + while (bi instanceof ICIChainMember + && incompatibleImprovements < ((ICIChainMember) bi).getMinimumICIVersion()) { + bi = (BuiltIn) ((ICIChainMember) bi).getPreviousICIChainMember(); + } + + try { + bi = (BuiltIn) bi.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + bi.key = key; + bi.target = target; + return bi; + } + + @Override + public String getCanonicalForm() { + return target.getCanonicalForm() + "?" + key; + } + + @Override + String getNodeTypeSymbol() { + return "?" + key; + } + + @Override + boolean isLiteral() { + return false; // be on the safe side. + } + + protected final void checkMethodArgCount(List args, int expectedCnt) throws TemplateModelException { + checkMethodArgCount(args.size(), expectedCnt); + } + + protected final void checkMethodArgCount(int argCnt, int expectedCnt) throws TemplateModelException { + if (argCnt != expectedCnt) { + throw MessageUtil.newArgCntError("?" + key, argCnt, expectedCnt); + } + } + + protected final void checkMethodArgCount(List args, int minCnt, int maxCnt) throws TemplateModelException { + checkMethodArgCount(args.size(), minCnt, maxCnt); + } + + protected final void checkMethodArgCount(int argCnt, int minCnt, int maxCnt) throws TemplateModelException { + if (argCnt < minCnt || argCnt > maxCnt) { + throw MessageUtil.newArgCntError("?" + key, argCnt, minCnt, maxCnt); + } + } + + /** + * Same as {@link #getStringMethodArg}, but checks if {@code args} is big enough, and returns {@code null} if it + * isn't. + */ + protected final String getOptStringMethodArg(List args, int argIdx) + throws TemplateModelException { + return args.size() > argIdx ? getStringMethodArg(args, argIdx) : null; + } + + /** + * Gets a method argument and checks if it's a string; it does NOT check if {@code args} is big enough. + */ + protected final String getStringMethodArg(List args, int argIdx) + throws TemplateModelException { + TemplateModel arg = (TemplateModel) args.get(argIdx); + if (!(arg instanceof TemplateScalarModel)) { + throw MessageUtil.newMethodArgMustBeStringException("?" + key, argIdx, arg); + } else { + return EvalUtil.modelToString((TemplateScalarModel) arg, null, null); + } + } + + /** + * Gets a method argument and checks if it's a number; it does NOT check if {@code args} is big enough. + */ + protected final Number getNumberMethodArg(List args, int argIdx) + throws TemplateModelException { + TemplateModel arg = (TemplateModel) args.get(argIdx); + if (!(arg instanceof TemplateNumberModel)) { + throw MessageUtil.newMethodArgMustBeNumberException("?" + key, argIdx, arg); + } else { + return EvalUtil.modelToNumber((TemplateNumberModel) arg, null); + } + } + + protected final TemplateModelException newMethodArgInvalidValueException(int argIdx, Object[] details) { + return MessageUtil.newMethodArgInvalidValueException("?" + key, argIdx, details); + } + + protected final TemplateModelException newMethodArgsInvalidValueException(Object[] details) { + return MessageUtil.newMethodArgsInvalidValueException("?" + key, details); + } + + @Override + protected Expression deepCloneWithIdentifierReplaced_inner( + String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) { + try { + BuiltIn clone = (BuiltIn) clone(); + clone.target = target.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState); + return clone; + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Internal error: " + e); + } + } + + @Override + int getParameterCount() { + return 2; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return target; + case 1: return key; + 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; + default: throw new IndexOutOfBoundsException(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInBannedWhenAutoEscaping.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInBannedWhenAutoEscaping.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInBannedWhenAutoEscaping.java new file mode 100644 index 0000000..c8701cf --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/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.ast; + +/** + * 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/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForDate.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForDate.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForDate.java new file mode 100644 index 0000000..9dc8ee6 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInForDate.java @@ -0,0 +1,57 @@ +/* + * 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.ast; + +import java.util.Date; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModel; + +abstract class BuiltInForDate extends BuiltIn { + @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, Expression 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/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForHashEx.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForHashEx.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForHashEx.java new file mode 100644 index 0000000..f67925f --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInForHashEx.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.ast; + +import org.apache.freemarker.core.TemplateException; +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 BuiltIn { + + @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/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForLegacyEscaping.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForLegacyEscaping.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForLegacyEscaping.java new file mode 100644 index 0000000..4c79a9c --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/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.ast; + +import org.apache.freemarker.core.TemplateException; +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/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForLoopVariable.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForLoopVariable.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForLoopVariable.java new file mode 100644 index 0000000..a55f82c --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/BuiltInForLoopVariable.java @@ -0,0 +1,49 @@ +/* + * 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.ast; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.ast.IteratorBlock.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 = IteratorBlock.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/ecb4e230/src/main/java/org/apache/freemarker/core/ast/BuiltInForMarkupOutput.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ast/BuiltInForMarkupOutput.java b/src/main/java/org/apache/freemarker/core/ast/BuiltInForMarkupOutput.java new file mode 100644 index 0000000..15f0f4b --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ast/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.ast; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +abstract class BuiltInForMarkupOutput extends BuiltIn { + + @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
