/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package freemarker.core;

import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;

An instruction that makes a single assignment, like [#local x=1]. This is also used as the child of AssignmentInstruction, if there are multiple assignments in the same tag, like in [#local x=1 x=2].
/** * 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);
Params:
  • variableName – the variable name to assign to.
  • valueExp – the expression to assign.
  • scope – the scope of the assignment, one of NAMESPACE, LOCAL, or GLOBAL
/** * @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); if (value == null) { if (env.isClassicCompatible()) { value = TemplateScalarModel.EMPTY_STRING; } else { throw InvalidReferenceException.getInstance(valueExp, 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) { if (env.isClassicCompatible()) { lhoValue = TemplateScalarModel.EMPTY_STRING; } else { throw InvalidReferenceException.getInstance(scope, variableName, getOperatorTypeAsString(), env); } } value = valueExp.eval(env); if (value == null) { if (env.isClassicCompatible()) { value = TemplateScalarModel.EMPTY_STRING; } else { throw InvalidReferenceException.getInstance(valueExp, 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(scope, 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) + "="; } } static String scopeAsString(int scope) { switch (scope) { case NAMESPACE: return "template namespace"; case LOCAL: return "local scope"; case GLOBAL: return "global scope"; default: throw new AssertionError("Unsupported scope: " + scope); } } }