/** * 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; import org.mvel2.ast.*; import org.mvel2.integration.GlobalListenerFactory; import org.mvel2.integration.VariableResolverFactory; import org.mvel2.integration.impl.ImmutableDefaultFactory; import org.mvel2.util.ErrorUtil; import org.mvel2.util.MethodStub; import org.mvel2.util.ParseTools; import org.mvel2.util.StringAppender; import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import static java.lang.Character.isJavaIdentifierPart; import static java.lang.Thread.currentThread; import static java.lang.reflect.Array.getLength; import static org.mvel2.DataConversion.canConvert; import static org.mvel2.DataConversion.convert; import static org.mvel2.MVEL.eval; import static org.mvel2.ast.TypeDescriptor.getClassReference; import static org.mvel2.compiler.AbstractParser.LITERALS; import static org.mvel2.integration.GlobalListenerFactory.notifySetListeners; import static org.mvel2.integration.PropertyHandlerFactory.*; import static org.mvel2.util.ParseTools.*; import static org.mvel2.util.PropertyTools.getFieldOrAccessor; import static org.mvel2.util.PropertyTools.getFieldOrWriteAccessor; import static org.mvel2.util.ReflectionUtil.toNonPrimitiveType; import static org.mvel2.util.Varargs.normalizeArgsForVarArgs; import static org.mvel2.util.Varargs.paramTypeVarArgsSafe; @SuppressWarnings({"unchecked"}) /** * The property accessor class is used for extracting properties from objects instances. */ public class PropertyAccessor { private int start = 0; private int cursor = 0; private int st; private char[] property; private int length; private int end; private Object thisReference; private Object ctx; private Object curr; private Class currType = null; private boolean first = true; private boolean nullHandle = false; private VariableResolverFactory variableFactory; private ParserContext pCtx; // private static final int DONE = -1; private static final int NORM = 0; private static final int METH = 1; private static final int COL = 2; private static final int WITH = 3; private static final Object[] EMPTYARG = new Object[0]; private static final Map<Class, WeakHashMap<Integer, WeakReference<Member>>> READ_PROPERTY_RESOLVER_CACHE; private static final Map<Class, WeakHashMap<Integer, WeakReference<Member>>> WRITE_PROPERTY_RESOLVER_CACHE; private static final Map<Class, WeakHashMap<Integer, WeakReference<Object[]>>> METHOD_RESOLVER_CACHE; private static final Map<Member, WeakReference<Class[]>> METHOD_PARMTYPES_CACHE; static { READ_PROPERTY_RESOLVER_CACHE = Collections.synchronizedMap(new WeakHashMap<Class, WeakHashMap<Integer, WeakReference<Member>>>(10)); WRITE_PROPERTY_RESOLVER_CACHE = Collections.synchronizedMap(new WeakHashMap<Class, WeakHashMap<Integer, WeakReference<Member>>>(10)); METHOD_RESOLVER_CACHE = Collections.synchronizedMap(new WeakHashMap<Class, WeakHashMap<Integer, WeakReference<Object[]>>>(10)); METHOD_PARMTYPES_CACHE = Collections.synchronizedMap(new WeakHashMap<Member, WeakReference<Class[]>>(10)); } public PropertyAccessor(String property, Object ctx) { this.length = end = (this.property = property.toCharArray()).length; this.ctx = ctx; this.variableFactory = new ImmutableDefaultFactory(); } public PropertyAccessor(char[] property, Object ctx, VariableResolverFactory resolver, Object thisReference, ParserContext pCtx) { this.length = end = (this.property = property).length; this.ctx = ctx; this.variableFactory = resolver; this.thisReference = thisReference; this.pCtx = pCtx; } public PropertyAccessor(char[] property, int start, int offset, Object ctx, VariableResolverFactory resolver, Object thisReference, ParserContext pCtx) { this.property = property; this.cursor = this.st = this.start = start; this.length = offset; this.end = start + offset; this.ctx = ctx; this.variableFactory = resolver; this.thisReference = thisReference; this.pCtx = pCtx; } public static Object get(String property, Object ctx) { return new PropertyAccessor(property, ctx).get(); } public static Object get(char[] property, int offset, int end, Object ctx, VariableResolverFactory resolver, Object thisReferece, ParserContext pCtx) { return new PropertyAccessor(property, offset, end, ctx, resolver, thisReferece, pCtx).get(); } public static Object get(String property, Object ctx, VariableResolverFactory resolver, Object thisReference, ParserContext pCtx) { return new PropertyAccessor(property.toCharArray(), ctx, resolver, thisReference, pCtx).get(); } public static void set(Object ctx, String property, Object value) { new PropertyAccessor(property, ctx).set(value); } public static void set(Object ctx, VariableResolverFactory resolver, String property, Object value, ParserContext pCtx) { new PropertyAccessor(property.toCharArray(), ctx, resolver, null, pCtx).set(value); } private Object get() { curr = ctx; try { if (!MVEL.COMPILER_OPT_ALLOW_OVERRIDE_ALL_PROPHANDLING) { return getNormal(); } else { return getAllowOverride(); } } catch (InvocationTargetException e) { throw new PropertyAccessException("could not access property", property, cursor, e, pCtx); } catch (IllegalAccessException e) { throw new PropertyAccessException("could not access property", property, cursor, e, pCtx); } catch (IndexOutOfBoundsException e) { if (cursor >= length) cursor = length -1; throw new PropertyAccessException("array or collections index out of bounds in property: " + new String(property, cursor, length), property, cursor, e, pCtx); } catch (CompileException e) { throw ErrorUtil.rewriteIfNeeded(e, property, st); } catch (NullPointerException e) { throw new PropertyAccessException("null pointer exception in property: " + new String(property), property, cursor, e, pCtx); } catch (Exception e) { throw new PropertyAccessException("unknown exception in expression: " + new String(property), property, cursor, e, pCtx); } } private Object getNormal() throws Exception { while (cursor < end) { switch (nextToken()) { case NORM: curr = getBeanProperty(curr, capture()); break; case METH: curr = getMethod(curr, capture()); break; case COL: curr = getCollectionProperty(curr, capture()); break; case WITH: curr = getWithProperty(curr); break; } if (nullHandle) { if (curr == null) { return null; } else { nullHandle = false; } } first = false; } return curr; } private Object getAllowOverride() throws Exception { while (cursor < end) { switch (nextToken()) { case NORM: if ((curr = getBeanPropertyAO(curr, capture())) == null && hasNullPropertyHandler()) { curr = getNullPropertyHandler().getProperty(capture(), ctx, variableFactory); } break; case METH: if ((curr = getMethod(curr, capture())) == null && hasNullMethodHandler()) { curr = getNullMethodHandler().getProperty(capture(), ctx, variableFactory); } break; case COL: curr = getCollectionPropertyAO(curr, capture()); break; case WITH: curr = getWithProperty(curr); break; } if (nullHandle) { if (curr == null) { return null; } else { nullHandle = false; } } else { if (curr == null && cursor < end) throw new NullPointerException(); } first = false; } return curr; } private void set(Object value) { curr = ctx; try { int oLength = end; end = findAbsoluteLast(property); if ((curr = get()) == null) throw new PropertyAccessException("cannot bind to null context: " + new String(property, cursor, length), property, cursor, pCtx); end = oLength; if (nextToken() == COL) { int _start = ++cursor; whiteSpaceSkip(); if (cursor == length || scanTo(']')) throw new PropertyAccessException("unterminated '['", property, cursor, pCtx); String ex = new String(property, _start, cursor - _start); if (!MVEL.COMPILER_OPT_ALLOW_OVERRIDE_ALL_PROPHANDLING) { if (curr instanceof Map) { //noinspection unchecked ((Map) curr).put(eval(ex, this.ctx, this.variableFactory), value); } else if (curr instanceof List) { //noinspection unchecked ((List) curr).set(eval(ex, this.ctx, this.variableFactory, Integer.class), value); } else if (hasPropertyHandler(curr.getClass())) { getPropertyHandler(curr.getClass()).setProperty(ex, ctx, variableFactory, value); } else if (curr.getClass().isArray()) { Array.set(curr, eval(ex, this.ctx, this.variableFactory, Integer.class), convert(value, getBaseComponentType(curr.getClass()))); } else { throw new PropertyAccessException("cannot bind to collection property: " + new String(property) + ": not a recognized collection type: " + ctx.getClass(), property, cursor, pCtx); } return; } else { notifySetListeners(ctx, ex, variableFactory, value); if (curr instanceof Map) { //noinspection unchecked if (hasPropertyHandler(Map.class)) getPropertyHandler(Map.class).setProperty(ex, curr, variableFactory, value); else ((Map) curr).put(eval(ex, this.ctx, this.variableFactory), value); } else if (curr instanceof List) { //noinspection unchecked if (hasPropertyHandler(List.class)) getPropertyHandler(List.class).setProperty(ex, curr, variableFactory, value); else ((List) curr).set(eval(ex, this.ctx, this.variableFactory, Integer.class), value); } else if (curr.getClass().isArray()) { if (hasPropertyHandler(Array.class)) getPropertyHandler(Array.class).setProperty(ex, curr, variableFactory, value); else Array.set(curr, eval(ex, this.ctx, this.variableFactory, Integer.class), convert(value, getBaseComponentType(curr.getClass()))); } else if (hasPropertyHandler(curr.getClass())) { getPropertyHandler(curr.getClass()).setProperty(ex, curr, variableFactory, value); } else { throw new PropertyAccessException("cannot bind to collection property: " + new String(property) + ": not a recognized collection type: " + ctx.getClass(), property, cursor, pCtx); } return; } } else if (MVEL.COMPILER_OPT_ALLOW_OVERRIDE_ALL_PROPHANDLING && hasPropertyHandler(curr.getClass())) { getPropertyHandler(curr.getClass()).setProperty(capture(), curr, variableFactory, value); return; } String tk = capture(); Member member = checkWriteCache(curr.getClass(), tk == null ? 0 : tk.hashCode()); if (member == null) { addWriteCache(curr.getClass(), tk != null ? tk.hashCode() : -1, (member = value != null ? getFieldOrWriteAccessor(curr.getClass(), tk, value.getClass()) : getFieldOrWriteAccessor(curr.getClass(), tk))); } if (member instanceof Method) { Method meth = (Method) member; Class[] parameterTypes = checkParmTypesCache(meth); if (value != null && !parameterTypes[0].isAssignableFrom(value.getClass())) { if (!canConvert(parameterTypes[0], value.getClass())) { throw new CompileException("cannot convert type: " + value.getClass() + ": to " + meth.getParameterTypes()[0], property, cursor); } meth.invoke(curr, convert(value, parameterTypes[0])); } else { meth.invoke(curr, value); } } else if (member != null) { Field fld = (Field) member; if (value != null && !fld.getType().isAssignableFrom(value.getClass())) { if (!canConvert(fld.getType(), value.getClass())) { throw new CompileException("cannot convert type: " + value.getClass() + ": to " + fld.getType(), property, cursor); } fld.set(curr, convert(value, fld.getType())); } else { fld.set(curr, value); } } else if (curr instanceof Map) { //noinspection unchecked ((Map) curr).put(eval(tk, this.ctx, this.variableFactory), value); } else if (curr instanceof FunctionInstance) { ((PrototypalFunctionInstance) curr).getResolverFactory().getVariableResolver(tk).setValue(value); } else { throw new PropertyAccessException("could not access/write property (" + tk + ") in: " + (curr == null ? "Unknown" : curr.getClass().getName()), property, cursor, pCtx); } } catch (InvocationTargetException e) { throw new PropertyAccessException("could not access property", property, st, e, pCtx); } catch (IllegalAccessException e) { throw new PropertyAccessException("could not access property", property, st, e, pCtx); } } private int nextToken() { switch (property[st = cursor]) { case '[': return COL; case '{': if (property[cursor - 1] == '.') { return WITH; } break; case '.': // ++cursor; while (cursor < end && isWhitespace(property[cursor])) cursor++; if ((st + 1) != end) { switch (property[cursor = ++st]) { case '?': cursor = ++st; nullHandle = true; break; case '{': return WITH; } } case '?': if (cursor == start) { cursor = ++st; nullHandle = true; } } do { while (cursor < end && isWhitespace(property[cursor])) cursor++; if (cursor < end && property[cursor] == '.') { cursor++; } else { break; } } while (true); st = cursor; //noinspection StatementWithEmptyBody while (++cursor < end && isJavaIdentifierPart(property[cursor])) ; if (cursor < end) { while (isWhitespace(property[cursor])) cursor++; switch (property[cursor]) { case '[': return COL; case '(': return METH; default: return 0; } } return 0; } private String capture() { return new String(property, st, trimLeft(cursor) - st); } protected int trimLeft(int pos) { while (pos > 0 && isWhitespace(property[pos - 1])) pos--; return pos; } public static void clearPropertyResolverCache() { READ_PROPERTY_RESOLVER_CACHE.clear(); WRITE_PROPERTY_RESOLVER_CACHE.clear(); METHOD_RESOLVER_CACHE.clear(); } public static void reportCacheSizes() { System.out.println("read property cache: " + READ_PROPERTY_RESOLVER_CACHE.size()); for (Class cls : READ_PROPERTY_RESOLVER_CACHE.keySet()) { System.out.println(" [" + cls.getName() + "]: " + READ_PROPERTY_RESOLVER_CACHE.get(cls).size() + " entries."); } System.out.println("write property cache: " + WRITE_PROPERTY_RESOLVER_CACHE.size()); for (Class cls : WRITE_PROPERTY_RESOLVER_CACHE.keySet()) { System.out.println(" [" + cls.getName() + "]: " + WRITE_PROPERTY_RESOLVER_CACHE.get(cls).size() + " entries."); } System.out.println("method cache: " + METHOD_RESOLVER_CACHE.size()); for (Class cls : METHOD_RESOLVER_CACHE.keySet()) { System.out.println(" [" + cls.getName() + "]: " + METHOD_RESOLVER_CACHE.get(cls).size() + " entries."); } } private static void addReadCache(Class cls, Integer property, Member member) { synchronized (READ_PROPERTY_RESOLVER_CACHE) { WeakHashMap<Integer, WeakReference<Member>> nestedMap = READ_PROPERTY_RESOLVER_CACHE.get(cls); if (nestedMap == null) { READ_PROPERTY_RESOLVER_CACHE.put(cls, nestedMap = new WeakHashMap<Integer, WeakReference<Member>>()); } nestedMap.put(property, new WeakReference<Member>(member)); } } private static Member checkReadCache(Class cls, Integer property) { WeakHashMap<Integer, WeakReference<Member>> map = READ_PROPERTY_RESOLVER_CACHE.get(cls); if (map != null) { WeakReference<Member> member = map.get(property); if (member != null) return member.get(); } return null; } private static void addWriteCache(Class cls, Integer property, Member member) { synchronized (WRITE_PROPERTY_RESOLVER_CACHE) { WeakHashMap<Integer, WeakReference<Member>> map = WRITE_PROPERTY_RESOLVER_CACHE.get(cls); if (map == null) { WRITE_PROPERTY_RESOLVER_CACHE.put(cls, map = new WeakHashMap<Integer, WeakReference<Member>>()); } map.put(property, new WeakReference<Member>(member)); } } private static Member checkWriteCache(Class cls, Integer property) { Map<Integer, WeakReference<Member>> map = WRITE_PROPERTY_RESOLVER_CACHE.get(cls); if (map != null) { WeakReference<Member> member = map.get(property); if (member != null) return member.get(); } return null; } public static Class[] checkParmTypesCache(Method member) { WeakReference<Class[]> pt = METHOD_PARMTYPES_CACHE.get(member); Class[] ret; if (pt == null || (ret = pt.get()) == null) { //noinspection UnusedAssignment METHOD_PARMTYPES_CACHE.put(member, pt = new WeakReference<Class[]>(ret = member.getParameterTypes())); } return ret; } private static void addMethodCache(Class cls, Integer property, Method member) { synchronized (METHOD_RESOLVER_CACHE) { WeakHashMap<Integer, WeakReference<Object[]>> map = METHOD_RESOLVER_CACHE.get(cls); if (map == null) { METHOD_RESOLVER_CACHE.put(cls, map = new WeakHashMap<Integer, WeakReference<Object[]>>()); } map.put(property, new WeakReference<Object[]>(new Object[]{member, member.getParameterTypes()})); } } private static Object[] checkMethodCache(Class cls, Integer property) { Map<Integer, WeakReference<Object[]>> map = METHOD_RESOLVER_CACHE.get(cls); if (map != null) { WeakReference<Object[]> ref = map.get(property); if (ref != null) return ref.get(); } return null; } private Object getBeanPropertyAO(Object ctx, String property) throws IllegalAccessException, InvocationTargetException { if (ctx != null && hasPropertyHandler(ctx.getClass())) return getPropertyHandler(ctx.getClass()).getProperty(property, ctx, variableFactory); GlobalListenerFactory.notifyGetListeners(ctx, property, variableFactory); return getBeanProperty(ctx, property); } private Object getBeanProperty(Object ctx, String property) throws IllegalAccessException, InvocationTargetException { if (first) { if ("this".equals(property)) { return this.ctx; } else if (LITERALS.containsKey(property)) { return LITERALS.get(property); } else if (variableFactory != null && variableFactory.isResolveable(property)) { return variableFactory.getVariableResolver(property).getValue(); } } if (ctx != null) { Class<?> cls; if (ctx instanceof Class) { if (MVEL.COMPILER_OPT_SUPPORT_JAVA_STYLE_CLASS_LITERALS && "class".equals(property)) { return ctx; } cls = (Class<?>) ctx; } else { cls = ctx.getClass(); } Member member = checkReadCache(cls, property.hashCode()); if (member == null) { addReadCache(cls, property.hashCode(), member = getFieldOrAccessor(cls, property)); } if (member instanceof Method) { try { return ((Method) member).invoke(ctx, EMPTYARG); } catch (IllegalAccessException e) { synchronized (member) { try { ((Method) member).setAccessible(true); return ((Method) member).invoke(ctx, EMPTYARG); } finally { ((Method) member).setAccessible(false); } } } catch (IllegalArgumentException e) { if (member.getDeclaringClass().equals(ctx)) { try { Class c = Class.forName(member.getDeclaringClass().getName() + "$" + property); throw new CompileException("name collision between innerclass: " + c.getCanonicalName() + "; and bean accessor: " + property + " (" + member.toString() + ")", this.property, this.st); } catch (ClassNotFoundException e2) { //fallthru } } throw e; } } else if (member != null) { currType = toNonPrimitiveType(((Field) member).getType()); return ((Field) member).get(ctx); } else if (ctx instanceof Map && (((Map) ctx).containsKey(property) || nullHandle)) { if (ctx instanceof Proto.ProtoInstance) { return ((Proto.ProtoInstance) ctx).get(property).call(null, thisReference, variableFactory, EMPTY_OBJ_ARR); } return ((Map) ctx).get(property); } else if ("length".equals(property) && ctx.getClass().isArray()) { return getLength(ctx); } else if (ctx instanceof Class) { Class c = (Class) ctx; for (Method m : c.getMethods()) { if (property.equals(m.getName())) { if (pCtx!=null&& pCtx.getParserConfiguration()!=null?pCtx.getParserConfiguration().isAllowNakedMethCall():MVEL.COMPILER_OPT_ALLOW_NAKED_METH_CALL) { return m.invoke(ctx, EMPTY_OBJ_ARR); } return m; } } try { return findClass(variableFactory, c.getName() + "$" + property, pCtx); } catch (ClassNotFoundException cnfe) { // fall through. } } else if (hasPropertyHandler(cls)) { return getPropertyHandler(cls).getProperty(property, ctx, variableFactory); } else if (ctx instanceof FunctionInstance) { return ((PrototypalFunctionInstance) ctx).getResolverFactory().getVariableResolver(property).getValue(); } } Object tryStatic = tryStaticAccess(); if (tryStatic != null) { if (tryStatic instanceof Class || tryStatic instanceof Method) return tryStatic; else { return ((Field) tryStatic).get(null); } } else if (pCtx!=null&& pCtx.getParserConfiguration()!=null?pCtx.getParserConfiguration().isAllowNakedMethCall():MVEL.COMPILER_OPT_ALLOW_NAKED_METH_CALL) { return getMethod(ctx, property); } if (ctx == null) { throw new PropertyAccessException("unresolvable property or identifier: " + property, this.property, st, pCtx); } else { throw new PropertyAccessException("could not access: " + property + "; in class: " + ctx.getClass().getName(), this.property, st, pCtx); } } private void whiteSpaceSkip() { if (cursor < end) //noinspection StatementWithEmptyBody while (isWhitespace(property[cursor]) && ++cursor < end) ; } /** * @param c - character to scan to. * @return - returns true is end of statement is hit, false if the scan scar is countered. */ private boolean scanTo(char c) { for (; cursor < end; cursor++) { switch (property[cursor]) { case '\'': case '"': cursor = captureStringLiteral(property[cursor], property, cursor, end); default: if (property[cursor] == c) { return false; } } } return true; } private Object getWithProperty(Object ctx) { int st; String nestParm = start == cursor ? null : new String(property, start, cursor - start - 1).trim(); parseWithExpressions(nestParm, property, st = cursor + 1, (cursor = balancedCaptureWithLineAccounting(property, cursor, end, '{', pCtx)) - st, ctx, variableFactory); cursor++; return ctx; } /** * Handle accessing a property embedded in a collections, map, or array * * @param ctx - * @param prop - * @return - * @throws Exception - */ private Object getCollectionProperty(Object ctx, String prop) throws Exception { if (prop.length() != 0) { ctx = getBeanProperty(ctx, prop); if (ctx == null) { throw new NullPointerException("null pointer on indexed access for: " + prop); } } currType = null; int _start = ++cursor; whiteSpaceSkip(); if (cursor == end || scanTo(']')) throw new PropertyAccessException("unterminated '['", property, cursor, pCtx); prop = new String(property, _start, cursor++ - _start); if (ctx instanceof Map) { return ((Map) ctx).get(eval(prop, ctx, variableFactory)); } else if (ctx instanceof List) { return ((List) ctx).get((Integer) eval(prop, ctx, variableFactory)); } else if (ctx instanceof Collection) { int count = (Integer) eval(prop, ctx, variableFactory); if (count > ((Collection) ctx).size()) throw new PropertyAccessException("index [" + count + "] out of bounds on collections", property, cursor, pCtx); Iterator iter = ((Collection) ctx).iterator(); for (int i = 0; i < count; i++) iter.next(); return iter.next(); } else if (ctx.getClass().isArray()) { return Array.get(ctx, (Integer) eval(prop, ctx, variableFactory)); } else if (ctx instanceof CharSequence) { return ((CharSequence) ctx).charAt((Integer) eval(prop, ctx, variableFactory)); } else { try { return getClassReference(pCtx, (Class) ctx, new TypeDescriptor(property, start, length, 0)); } catch (Exception e) { throw new PropertyAccessException("illegal use of []: unknown type: " + (ctx.getClass().getName()), property, st, e, pCtx); } } } private Object getCollectionPropertyAO(Object ctx, String prop) throws Exception { if (prop.length() != 0) { ctx = getBeanProperty(ctx, prop); } currType = null; if (ctx == null) return null; int _start = ++cursor; whiteSpaceSkip(); if (cursor == end || scanTo(']')) throw new PropertyAccessException("unterminated '['", property, cursor, pCtx); prop = new String(property, _start, cursor++ - _start); if (ctx instanceof Map) { if (hasPropertyHandler(Map.class)) return getPropertyHandler(Map.class).getProperty(prop, ctx, variableFactory); else return ((Map) ctx).get(eval(prop, ctx, variableFactory)); } else if (ctx instanceof List) { if (hasPropertyHandler(List.class)) return getPropertyHandler(List.class).getProperty(prop, ctx, variableFactory); else return ((List) ctx).get((Integer) eval(prop, ctx, variableFactory)); } else if (ctx instanceof Collection) { if (hasPropertyHandler(Collection.class)) return getPropertyHandler(Collection.class).getProperty(prop, ctx, variableFactory); else { int count = (Integer) eval(prop, ctx, variableFactory); if (count > ((Collection) ctx).size()) throw new PropertyAccessException("index [" + count + "] out of bounds on collections", property, cursor, pCtx); Iterator iter = ((Collection) ctx).iterator(); for (int i = 0; i < count; i++) iter.next(); return iter.next(); } } else if (ctx.getClass().isArray()) { if (hasPropertyHandler(Array.class)) return getPropertyHandler(Array.class).getProperty(prop, ctx, variableFactory); return Array.get(ctx, (Integer) eval(prop, ctx, variableFactory)); } else if (ctx instanceof CharSequence) { if (hasPropertyHandler(CharSequence.class)) return getPropertyHandler(CharSequence.class).getProperty(prop, ctx, variableFactory); else return ((CharSequence) ctx).charAt((Integer) eval(prop, ctx, variableFactory)); } else { try { return getClassReference(pCtx, (Class) ctx, new TypeDescriptor(property, start, end - start, 0)); } catch (Exception e) { throw new PropertyAccessException("illegal use of []: unknown type: " + (ctx.getClass().getName()), property, st, pCtx); } } } /** * Find an appropriate method, execute it, and return it's response. * * @param ctx - * @param name - * @return - */ @SuppressWarnings({"unchecked"}) private Object getMethod(Object ctx, String name) { int _start = cursor; String tk = cursor != end && property[cursor] == '(' && ((cursor = balancedCapture(property, cursor, '(')) - _start) > 1 ? new String(property, _start + 1, cursor - _start - 1) : ""; cursor++; Object[] args; if (tk.length() == 0) { args = ParseTools.EMPTY_OBJ_ARR; } else { List<char[]> subtokens = parseParameterList(tk.toCharArray(), 0, -1); args = new Object[subtokens.size()]; for (int i = 0; i < subtokens.size(); i++) { args[i] = eval(subtokens.get(i), thisReference, variableFactory); } } if (first && variableFactory != null && variableFactory.isResolveable(name)) { Object ptr = variableFactory.getVariableResolver(name).getValue(); if (ptr instanceof Method) { ctx = ((Method) ptr).getDeclaringClass(); name = ((Method) ptr).getName(); } else if (ptr instanceof MethodStub) { ctx = ((MethodStub) ptr).getClassReference(); name = ((MethodStub) ptr).getMethodName(); } else if (ptr instanceof FunctionInstance) { ((FunctionInstance) ptr).getFunction().checkArgumentCount(args.length); return ((FunctionInstance) ptr).call(null, thisReference, variableFactory, args); } else { throw new OptimizationFailure("attempt to optimize a method call for a reference that does not point to a method: " + name + " (reference is type: " + (ctx != null ? ctx.getClass().getName() : null) + ")"); } first = false; } if (ctx == null) throw new CompileException("no such method or function: " + name, property, cursor); /** * If the target object is an instance of java.lang.Class itself then do not * adjust the Class scope target. */ Class cls = currType != null ? currType : ((ctx instanceof Class ? (Class) ctx : ctx.getClass())); currType = null; if (cls == Proto.ProtoInstance.class) { return ((Proto.ProtoInstance) ctx).get(name).call(null, thisReference, variableFactory, args); } /** * Check to see if we have already cached this method; */ Object[] cache = checkMethodCache(cls, createSignature(name, tk)); Method m; Class[] parameterTypes; if (cache != null) { m = (Method) cache[0]; parameterTypes = (Class[]) cache[1]; } else { m = null; parameterTypes = null; } /** * If we have not cached the method then we need to go ahead and try to resolve it. */ if (m == null) { /** * Try to find an instance method from the class target. */ if ((m = getBestCandidate(args, name, cls, cls.getMethods(), false)) != null) { addMethodCache(cls, createSignature(name, tk), m); parameterTypes = m.getParameterTypes(); } if (m == null) { /** * If we didn't find anything, maybe we're looking for the actual java.lang.Class methods. */ if ((m = getBestCandidate(args, name, cls, cls.getDeclaredMethods(), false)) != null) { addMethodCache(cls, createSignature(name, tk), m); parameterTypes = m.getParameterTypes(); } } } // If we didn't find anything and the declared class is different from the actual one try also with the actual one if (m == null && cls != ctx.getClass() && !(ctx instanceof Class)) { cls = ctx.getClass(); if ((m = getBestCandidate(args, name, cls, cls.getDeclaredMethods(), false)) != null) { addMethodCache(cls, createSignature(name, tk), m); parameterTypes = m.getParameterTypes(); } } if (ctx instanceof PrototypalFunctionInstance) { final VariableResolverFactory funcCtx = ((PrototypalFunctionInstance) ctx).getResolverFactory(); Object prop = funcCtx.getVariableResolver(name).getValue(); if (prop instanceof PrototypalFunctionInstance) { return ((PrototypalFunctionInstance) prop).call(ctx, thisReference, new InvokationContextFactory(variableFactory, funcCtx), args); } } if (m == null) { StringAppender errorBuild = new StringAppender(); for (int i = 0; i < args.length; i++) { errorBuild.append(args[i] != null ? args[i].getClass().getName() : null); if (i < args.length - 1) errorBuild.append(", "); } if ("size".equals(name) && args.length == 0 && cls.isArray()) { return getLength(ctx); } // System.out.println("{ " + new String(property) + " }"); throw new PropertyAccessException("unable to resolve method: " + cls.getName() + "." + name + "(" + errorBuild.toString() + ") [arglength=" + args.length + "]" , property, st, pCtx); } else { for (int i = 0; i < args.length; i++) { args[i] = convert(args[i], paramTypeVarArgsSafe(parameterTypes, i, m.isVarArgs())); } /** * Invoke the target method and return the response. */ currType = toNonPrimitiveType(m.getReturnType()); try { return m.invoke(ctx, normalizeArgsForVarArgs(parameterTypes, args, m.isVarArgs())); } catch (IllegalAccessException e) { try { addMethodCache(cls, createSignature(name, tk), (m = getWidenedTarget(m))); return m.invoke(ctx, args); } catch (Exception e2) { throw new PropertyAccessException("unable to invoke method: " + name, property, cursor, e2, pCtx); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new PropertyAccessException("unable to invoke method: " + name, property, cursor, e, pCtx); } } } private static int createSignature(String name, String args) { return name.hashCode() + args.hashCode(); } private ClassLoader getClassLoader() { return pCtx != null ? pCtx.getClassLoader() : currentThread().getContextClassLoader(); } /** * Try static access of the property, and return an instance of the Field, Method of Class if successful. * * @return - Field, Method or Class instance. */ protected Object tryStaticAccess() { int begin = cursor; try { /** * Try to resolve this *smartly* as a static class reference. * * This starts at the end of the token and starts to step backwards to figure out whether * or not this may be a static class reference. We search for method calls simply by * inspecting for ()'s. The first union area we come to where no brackets are present is our * test-point for a class reference. If we find a class, we pass the reference to the * property accessor along with trailing methods (if any). * */ boolean meth = false; int last = end; for (int i = end - 1; i > start; i--) { switch (property[i]) { case '.': if (!meth) { try { String test = new String(property, start, (cursor = last) - start); if (MVEL.COMPILER_OPT_SUPPORT_JAVA_STYLE_CLASS_LITERALS && test.endsWith(".class")) test = test.substring(0, test.length() - 6); return getClassLoader().loadClass(test); } catch (ClassNotFoundException e) { Class cls = getClassLoader().loadClass(new String(property, start, i - start)); String name = new String(property, i + 1, end - i - 1); try { return cls.getField(name); } catch (NoSuchFieldException nfe) { for (Method m : cls.getMethods()) { if (name.equals(m.getName())) return m; } return null; } } } meth = false; last = i; break; case '}': i--; for (int d = 1; i > 0 && d != 0; i--) { switch (property[i]) { case '}': d++; break; case '{': d--; break; case '"': case '\'': char s = property[i]; while (i > 0 && (property[i] != s && property[i - 1] != '\\')) i--; } } break; case ')': i--; for (int d = 1; i > 0 && d != 0; i--) { switch (property[i]) { case ')': d++; break; case '(': d--; break; case '"': case '\'': char s = property[i]; while (i > 0 && (property[i] != s && property[i - 1] != '\\')) i--; } } meth = true; last = i++; break; case '\'': while (--i > 0) { if (property[i] == '\'' && property[i - 1] != '\\') { break; } } break; case '"': while (--i > 0) { if (property[i] == '"' && property[i - 1] != '\\') { break; } } break; } } } catch (Exception cnfe) { cursor = begin; } return null; } }