in src/main/java/org/apache/commons/jexl3/internal/Interpreter.java [510:692]
protected Object executeAssign(final JexlNode node, final JexlOperator assignop, final Object data) { // CSOFF: MethodLength
cancelCheck(node);
// left contains the reference to assign to
final JexlNode left = node.jjtGetChild(0);
final ASTIdentifier variable;
Object object = null;
final int symbol;
// check var decl with assign is ok
if (left instanceof ASTIdentifier) {
variable = (ASTIdentifier) left;
symbol = variable.getSymbol();
if (symbol >= 0) {
if (variable.isLexical() || options.isLexical()) {
if (variable instanceof ASTVar) {
if (!defineVariable((ASTVar) variable, block)) {
return redefinedVariable(variable, variable.getName());
}
} else if (variable.isShaded() && (variable.isLexical() || options.isLexicalShade())) {
return undefinedVariable(variable, variable.getName());
}
}
if (variable.isCaptured() && options.isConstCapture()) {
return constVariable(variable, variable.getName());
}
}
} else {
variable = null;
symbol = -1;
}
boolean antish = options.isAntish();
// 0: determine initial object & property:
final int last = left.jjtGetNumChildren() - 1;
// right is the value expression to assign
final Object right = node.jjtGetNumChildren() < 2? null: node.jjtGetChild(1).jjtAccept(this, data);
// actual value to return, right in most cases
Object actual = right;
// a (var?) v = ... expression
if (variable != null) {
if (symbol >= 0) {
// check we are not assigning a symbol itself
if (last < 0) {
if (assignop == null) {
// make the closure accessible to itself, ie capture the currently set variable after frame creation
if (right instanceof Closure) {
final Closure closure = (Closure) right;
// the variable scope must be the parent of the lambdas
closure.captureSelfIfRecursive(frame, symbol);
}
frame.set(symbol, right);
} else {
// go through potential overload
final Object self = getVariable(frame, block, variable);
final Consumer<Object> f = r -> frame.set(symbol, r);
actual = operators.tryAssignOverload(node, assignop, f, self, right);
}
return actual; // 1
}
object = getVariable(frame, block, variable);
// top level is a symbol, cannot be an antish var
antish = false;
} else {
// check we are not assigning direct global
final String name = variable.getName();
if (last < 0) {
if (assignop == null) {
setContextVariable(node, name, right);
} else {
// go through potential overload
final Object self = context.get(name);
final Consumer<Object> f = r -> setContextVariable(node, name, r);
actual = operators.tryAssignOverload(node, assignop, f, self, right);
}
return actual; // 2
}
object = context.get(name);
// top level accesses object, cannot be an antish var
if (object != null) {
antish = false;
}
}
} else if (!(left instanceof ASTReference)) {
throw new JexlException(left, "illegal assignment form 0");
}
// 1: follow children till penultimate, resolve dot/array
JexlNode objectNode = null;
StringBuilder ant = null;
int v = 1;
// start at 1 if symbol
main: for (int c = symbol >= 0 ? 1 : 0; c < last; ++c) {
objectNode = left.jjtGetChild(c);
object = objectNode.jjtAccept(this, object);
if (object != null) {
// disallow mixing antish variable & bean with same root; avoid ambiguity
antish = false;
} else if (antish) {
// initialize if first time
if (ant == null) {
final JexlNode first = left.jjtGetChild(0);
final ASTIdentifier firstId = first instanceof ASTIdentifier
? (ASTIdentifier) first
: null;
if (firstId == null || firstId.getSymbol() >= 0) {
// ant remains null, object is null, stop solving
antish = false;
break main;
}
ant = new StringBuilder(firstId.getName());
}
// catch up to current child
for (; v <= c; ++v) {
final JexlNode child = left.jjtGetChild(v);
final ASTIdentifierAccess aid = child instanceof ASTIdentifierAccess
? (ASTIdentifierAccess) child
: null;
// remain antish only if unsafe navigation
if (aid == null || aid.isSafe() || aid.isExpression()) {
antish = false;
break main;
}
ant.append('.');
ant.append(aid.getName());
}
// solve antish
object = context.get(ant.toString());
} else {
throw new JexlException(objectNode, "illegal assignment form");
}
}
// 2: last objectNode will perform assignment in all cases
JexlNode propertyNode = left.jjtGetChild(last);
final ASTIdentifierAccess propertyId = propertyNode instanceof ASTIdentifierAccess
? (ASTIdentifierAccess) propertyNode
: null;
final Object property;
if (propertyId != null) {
// deal with creating/assigning antish variable
if (antish && ant != null && object == null && !propertyId.isSafe() && !propertyId.isExpression()) {
ant.append('.');
ant.append(propertyId.getName());
final String name = ant.toString();
if (assignop == null) {
setContextVariable(propertyNode, name, right);
} else {
final Object self = context.get(ant.toString());
final JexlNode pnode = propertyNode;
final Consumer<Object> assign = r -> setContextVariable(pnode, name, r);
actual = operators.tryAssignOverload(node, assignop, assign, self, right);
}
return actual; // 3
}
// property of an object ?
property = evalIdentifier(propertyId);
} else if (propertyNode instanceof ASTArrayAccess) {
// can have multiple nodes - either an expression, integer literal or reference
final int numChildren = propertyNode.jjtGetNumChildren() - 1;
for (int i = 0; i < numChildren; i++) {
final JexlNode nindex = propertyNode.jjtGetChild(i);
final Object index = nindex.jjtAccept(this, null);
object = getAttribute(object, index, nindex);
}
propertyNode = propertyNode.jjtGetChild(numChildren);
property = propertyNode.jjtAccept(this, null);
} else {
throw new JexlException(objectNode, "illegal assignment form");
}
// we may have a null property as in map[null], no check needed.
// we cannot *have* a null object though.
if (object == null) {
// no object, we fail
return unsolvableProperty(objectNode, "<null>.<?>", true, null);
}
// 3: one before last, assign
if (assignop == null) {
setAttribute(object, property, right, propertyNode);
} else {
final Object self = getAttribute(object, property, propertyNode);
final Object o = object;
final JexlNode n = propertyNode;
final Consumer<Object> assign = r -> setAttribute(o, property, r, n);
actual = operators.tryAssignOverload(node, assignop, assign, self, right);
}
return actual;
}