/** * 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.util; import org.mvel2.CompileException; import org.mvel2.DataConversion; import org.mvel2.ParserContext; import org.mvel2.compiler.ExecutableStatement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.mvel2.util.ParseTools.*; import static org.mvel2.util.ReflectionUtil.isAssignableFrom; /** * This is the inline collection sub-parser. It produces a skeleton model of the collection which is in turn translated * into a sequenced AST to produce the collection efficiently at runtime, and passed off to one of the JIT's if * configured. * * @author Christopher Brock */ public class CollectionParser { private char[] property; private int cursor; private int start; private int end; private int type; public static final int LIST = 0; public static final int ARRAY = 1; public static final int MAP = 2; private Class colType; private ParserContext pCtx; private static final Object[] EMPTY_ARRAY = new Object[0]; public CollectionParser() { } public CollectionParser(int type) { this.type = type; } public Object parseCollection(char[] property, int start, int offset, boolean subcompile, ParserContext pCtx) { this.property = property; this.pCtx = pCtx; this.end = start + offset; while (start < end && isWhitespace(property[start])) { start++; } this.start = this.cursor = start; return parseCollection(subcompile); } public Object parseCollection(char[] property, int start, int offset, boolean subcompile, Class colType, ParserContext pCtx) { if (colType != null) this.colType = getBaseComponentType(colType); this.property = property; this.end = start + offset; while (start < end && isWhitespace(property[start])) { start++; } this.start = this.cursor = start; this.pCtx = pCtx; return parseCollection(subcompile); } private Object parseCollection(boolean subcompile) { if (end - start == 0) { if (type == LIST) return new ArrayList(); else return EMPTY_ARRAY; } Map<Object, Object> map = null; List<Object> list = null; int st = start; if (type != -1) { switch (type) { case ARRAY: case LIST: list = new ArrayList<Object>(); break; case MAP: map = new HashMap<Object, Object>(); break; } } Object curr = null; int newType = -1; for (; cursor < end; cursor++) { switch (property[cursor]) { case '{': if (newType == -1) { newType = ARRAY; } case '[': if (cursor > start && isIdentifierPart(property[cursor - 1])) continue; if (newType == -1) { newType = LIST; } /** * Sub-parse nested collections. */ Object o = new CollectionParser(newType).parseCollection(property, (st = cursor) + 1, (cursor = balancedCapture(property, st, end, property[st])) - st - 1, subcompile, colType, pCtx); if (type == MAP) { map.put(curr, o); } else { list.add(curr = o); } cursor = skipWhitespace(property, ++cursor); if ((st = cursor) < end && property[cursor] == ',') { st = cursor + 1; } else if (cursor < end) { if (ParseTools.opLookup(property[cursor]) == -1) { throw new CompileException("unterminated collection element", property, cursor); } } continue; case '(': cursor = balancedCapture(property, cursor, end, '('); break; case '\"': case '\'': cursor = balancedCapture(property, cursor, end, property[cursor]); break; case ',': if (type != MAP) { list.add(new String(property, st, cursor - st).trim()); } else { map.put(curr, createStringTrimmed(property, st, cursor - st)); } if (subcompile) { subCompile(st, cursor - st); } st = cursor + 1; break; case ':': if (type != MAP) { map = new HashMap<Object, Object>(); type = MAP; } curr = createStringTrimmed(property, st, cursor - st); if (subcompile) { subCompile(st, cursor - st); } st = cursor + 1; break; case '.': cursor++; cursor = skipWhitespace(property, cursor); if (cursor != end && property[cursor] == '{') { cursor = balancedCapture(property, cursor, '{'); } break; } } if (st < end && isWhitespace(property[st])) { st = skipWhitespace(property, st); } if (st < end) { if (cursor < (end - 1)) cursor++; if (type == MAP) { map.put(curr, createStringTrimmed(property, st, cursor - st)); } else { if (cursor < end) cursor++; list.add(createStringTrimmed(property, st, cursor - st)); } if (subcompile) subCompile(st, cursor - st); } switch (type) { case MAP: return map; case ARRAY: return list.toArray(); default: return list; } } private void subCompile(int start, int offset) { if (colType == null) { subCompileExpression(property, start, offset, pCtx); } else { Class r = ((ExecutableStatement) subCompileExpression(property, start, offset, pCtx)).getKnownEgressType(); if (r != null && !isAssignableFrom(colType, r) && (isStrongType() || !DataConversion.canConvert(r, colType))) { throw new CompileException("expected type: " + colType.getName() + "; but found: " + r.getName(), property, cursor); } } } private boolean isStrongType() { return pCtx != null && pCtx.isStrongTyping(); } public int getCursor() { return cursor; } }