| /* |
| * 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 {@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); |
| 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( |
| 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(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) + "="; |
| } |
| } |
| |
| } |