/**
* 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.compiler.AbstractParser.getCurrentThreadParserContext;
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);
}
catch (IllegalAccessException e) {
throw new PropertyAccessException("could not access property", property, cursor, e);
}
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);
}
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);
}
catch (Exception e) {
throw new PropertyAccessException("unknown exception in expression: " + new String(property), property, cursor, e);
}
}
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);
end = oLength;
if (nextToken() == COL) {
int _start = ++cursor;
whiteSpaceSkip();
if (cursor == length || scanTo(']'))
throw new PropertyAccessException("unterminated '['", property, cursor);
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);
}
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);
}
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);
}
}
catch (InvocationTargetException e) {
throw new PropertyAccessException("could not access property", property, st, e);
}
catch (IllegalAccessException e) {
throw new PropertyAccessException("could not access property", property, st, e);
}
}
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);
}
else {
throw new PropertyAccessException("could not access: " + property + "; in class: " + ctx.getClass().getName(), this.property, st);
}
}
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,
'{', getCurrentThreadParserContext())) - 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);
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);
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(getCurrentThreadParserContext(), (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);
}
}
}
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);
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);
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(getCurrentThreadParserContext(), (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);
}
}
}
/**
* 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);
}
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);
}
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new PropertyAccessException("unable to invoke method: " + name, property, cursor, e);
}
}
}
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;
}
}