blob: bdce203ddda4bb29bfc5905fda8164e290a25865 [file] [log] [blame]
/*
* 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) + "=";
}
}
}