/**
* MVEL 2.0
* Copyright (C) 2007 The Codehaus
* Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor
*
* Licensed 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.mvel2.ast;
import org.mvel2.CompileException;
import org.mvel2.DataConversion;
import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import org.mvel2.compiler.ExecutableStatement;
import org.mvel2.integration.VariableResolverFactory;
import org.mvel2.integration.impl.DefaultLocalVariableResolverFactory;
import org.mvel2.integration.impl.ItemResolverFactory;
import org.mvel2.util.ParseTools;
import java.lang.reflect.Array;
import static org.mvel2.util.ParseTools.*;
/**
* @author Christopher Brock
*/
public class ForEachNode extends BlockNode {
protected String item;
protected Class itemType;
protected ExecutableStatement condition;
private static final int ITERABLE = 0;
private static final int ARRAY = 1;
private static final int CHARSEQUENCE = 2;
private static final int INTEGER = 3;
private int type = -1;
public ForEachNode(char[] expr, int start, int offset, int blockStart, int blockOffset, int fields, ParserContext pCtx) {
super(pCtx);
handleCond(this.expr = expr, this.start = start, this.offset = offset, this.fields = fields, pCtx);
this.blockStart = blockStart;
this.blockOffset = blockOffset;
if ((fields & COMPILE_IMMEDIATE) != 0) {
if (pCtx.isStrictTypeEnforcement() && itemType != null) {
pCtx = pCtx.createSubcontext();
pCtx.addInput(item, itemType);
}
pCtx.pushVariableScope();
pCtx.makeVisible(item);
this.compiledBlock = (ExecutableStatement) subCompileExpression(expr, blockStart, blockOffset, pCtx);
pCtx.popVariableScope();
}
}
public Object getReducedValueAccelerated(Object ctx, Object thisValue, VariableResolverFactory factory) {
ItemResolverFactory.ItemResolver itemR = new ItemResolverFactory.ItemResolver(item);
ItemResolverFactory itemFactory = new ItemResolverFactory(itemR, new DefaultLocalVariableResolverFactory(factory));
Object iterCond = condition.getValue(ctx, thisValue, factory);
if (type == -1) {
determineIterType(iterCond.getClass());
}
Object v;
switch (type) {
case ARRAY:
int len = Array.getLength(iterCond);
for (int i = 0; i < len; i++) {
itemR.setValue(Array.get(iterCond, i));
v = compiledBlock.getValue(ctx, thisValue, itemFactory);
if (itemFactory.tiltFlag()) return v;
}
break;
case CHARSEQUENCE:
for (Object o : iterCond.toString().toCharArray()) {
itemR.setValue(o);
v = compiledBlock.getValue(ctx, thisValue, itemFactory);
if (itemFactory.tiltFlag()) return v;
}
break;
case INTEGER:
int max = (Integer) iterCond + 1;
for (int i = 1; i != max; i++) {
itemR.setValue(i);
v = compiledBlock.getValue(ctx, thisValue, itemFactory);
if (itemFactory.tiltFlag()) return v;
}
break;
case ITERABLE:
for (Object o : (Iterable) iterCond) {
itemR.setValue(o);
v = compiledBlock.getValue(ctx, thisValue, itemFactory);
if (itemFactory.tiltFlag()) return v;
}
break;
}
return null;
}
public Object getReducedValue(Object ctx, Object thisValue, VariableResolverFactory factory) {
ItemResolverFactory.ItemResolver itemR = new ItemResolverFactory.ItemResolver(item);
ItemResolverFactory itemFactory = new ItemResolverFactory(itemR, new DefaultLocalVariableResolverFactory(factory));
Object iterCond = MVEL.eval(expr, start, offset, thisValue, factory);
if (itemType != null && itemType.isArray())
enforceTypeSafety(itemType, getBaseComponentType(iterCond.getClass()));
this.compiledBlock = (ExecutableStatement) subCompileExpression(expr, blockStart, blockOffset);
Object v;
if (iterCond instanceof Iterable) {
for (Object o : (Iterable) iterCond) {
itemR.setValue(o);
v = compiledBlock.getValue(ctx, thisValue, itemFactory);
if (itemFactory.tiltFlag()) return v;
}
}
else if (iterCond != null && iterCond.getClass().isArray()) {
int len = Array.getLength(iterCond);
for (int i = 0; i < len; i++) {
itemR.setValue(Array.get(iterCond, i));
v = compiledBlock.getValue(ctx, thisValue, itemFactory);
if (itemFactory.tiltFlag()) return v;
}
}
else if (iterCond instanceof CharSequence) {
for (Object o : iterCond.toString().toCharArray()) {
itemR.setValue(o);
v = compiledBlock.getValue(ctx, thisValue, itemFactory);
if (itemFactory.tiltFlag()) return v;
}
}
else if (iterCond instanceof Integer) {
int max = (Integer) iterCond + 1;
for (int i = 1; i != max; i++) {
itemR.setValue(i);
v = compiledBlock.getValue(ctx, thisValue, itemFactory);
if (itemFactory.tiltFlag()) return v;
}
}
else {
throw new CompileException("non-iterable type: "
+ (iterCond != null ? iterCond.getClass().getName() : "null"), expr, start);
}
return null;
}
private void handleCond(char[] condition, int start, int offset, int fields, ParserContext pCtx) {
int cursor = start;
int end = start + offset;
while (cursor < end && condition[cursor] != ':') cursor++;
if (cursor == end || condition[cursor] != ':')
throw new CompileException("expected : in foreach", condition, cursor);
int x;
if ((x = (item = createStringTrimmed(condition, start, cursor - start)).indexOf(' ')) != -1) {
String tk = new String(condition, start, x).trim();
try {
itemType = ParseTools.findClass(null, tk, pCtx);
item = new String(condition, start + x, (cursor - start) - x).trim();
}
catch (ClassNotFoundException e) {
throw new CompileException("cannot resolve identifier: " + tk, condition, start);
}
}
// this.start = ++cursor;
this.start = cursor + 1;
this.offset = offset - (cursor - start) - 1;
if ((fields & COMPILE_IMMEDIATE) != 0) {
Class egress = (this.condition = (ExecutableStatement) subCompileExpression(expr, this.start, this.offset, pCtx)).getKnownEgressType();
if (itemType != null && egress.isArray()) {
enforceTypeSafety(itemType, getBaseComponentType(this.condition.getKnownEgressType()));
}
else if (pCtx.isStrongTyping()) {
determineIterType(egress);
}
}
}
private void determineIterType(Class t) {
if (Iterable.class.isAssignableFrom(t)) {
type = ITERABLE;
}
else if (t.isArray()) {
type = ARRAY;
}
else if (CharSequence.class.isAssignableFrom(t)) {
type = CHARSEQUENCE;
}
else if (Integer.class.isAssignableFrom(t)) {
type = INTEGER;
}
else {
throw new CompileException("non-iterable type: " + t.getName(), expr, start);
}
}
private void enforceTypeSafety(Class required, Class actual) {
if (!required.isAssignableFrom(actual) && !DataConversion.canConvert(actual, required)) {
throw new CompileException("type mismatch in foreach: expected: "
+ required.getName() + "; but found: " + getBaseComponentType(actual), expr, start);
}
}
}