org.mule.mvel2.PropertyAccessor Maven / Gradle / Ivy
Go to download
MVEL is a powerful expression language for Java-based applications.
It provides a plethora of features and is suited for everything
from the smallest property binding and extraction, to full blown scripts.
This is a fork of MVEL customized for use in Mule.
/**
* 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.mule.mvel2;
import org.mule.mvel2.ast.Function;
import org.mule.mvel2.ast.Proto;
import org.mule.mvel2.ast.TypeDescriptor;
import org.mule.mvel2.integration.GlobalListenerFactory;
import org.mule.mvel2.integration.VariableResolverFactory;
import org.mule.mvel2.integration.impl.ImmutableDefaultFactory;
import org.mule.mvel2.util.ErrorUtil;
import org.mule.mvel2.util.MethodStub;
import org.mule.mvel2.util.ParseTools;
import org.mule.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.mule.mvel2.DataConversion.canConvert;
import static org.mule.mvel2.DataConversion.convert;
import static org.mule.mvel2.MVEL.eval;
import static org.mule.mvel2.ast.TypeDescriptor.getClassReference;
import static org.mule.mvel2.compiler.AbstractParser.LITERALS;
import static org.mule.mvel2.compiler.AbstractParser.getCurrentThreadParserContext;
import static org.mule.mvel2.integration.GlobalListenerFactory.notifySetListeners;
import static org.mule.mvel2.integration.PropertyHandlerFactory.*;
import static org.mule.mvel2.util.ParseTools.*;
import static org.mule.mvel2.util.PropertyTools.getFieldOrAccessor;
import static org.mule.mvel2.util.PropertyTools.getFieldOrWriteAccessor;
import static org.mule.mvel2.util.ReflectionUtil.toNonPrimitiveType;
import static org.mule.mvel2.util.Varargs.*;
@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 WeakHashMap>> READ_PROPERTY_RESOLVER_CACHE;
private static final WeakHashMap>> WRITE_PROPERTY_RESOLVER_CACHE;
private static final WeakHashMap>> METHOD_RESOLVER_CACHE;
private static final WeakHashMap> METHOD_PARMTYPES_CACHE;
static {
READ_PROPERTY_RESOLVER_CACHE = (new WeakHashMap>>(10));
WRITE_PROPERTY_RESOLVER_CACHE = (new WeakHashMap>>(10));
METHOD_RESOLVER_CACHE = (new WeakHashMap>>(10));
METHOD_PARMTYPES_CACHE = new WeakHashMap>(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) {
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[] paramaterTypes = checkParmTypesCache(meth);
if (value != null && !paramaterTypes[0].isAssignableFrom(value.getClass())) {
if (!canConvert(paramaterTypes[0], value.getClass())) {
throw new CompileException("cannot convert type: "
+ value.getClass() + ": to " + meth.getParameterTypes()[0], property, cursor);
}
meth.invoke(curr, convert(value, paramaterTypes[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 {
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> nestedMap = READ_PROPERTY_RESOLVER_CACHE.get(cls);
if (nestedMap == null) {
READ_PROPERTY_RESOLVER_CACHE.put(cls, nestedMap = new WeakHashMap>());
}
nestedMap.put(property, new WeakReference(member));
}
}
private static Member checkReadCache(Class cls, Integer property) {
WeakHashMap> map = READ_PROPERTY_RESOLVER_CACHE.get(cls);
if (map != null) {
WeakReference 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> map = WRITE_PROPERTY_RESOLVER_CACHE.get(cls);
if (map == null) {
WRITE_PROPERTY_RESOLVER_CACHE.put(cls, map = new WeakHashMap>());
}
map.put(property, new WeakReference(member));
}
}
private static Member checkWriteCache(Class cls, Integer property) {
Map> map = WRITE_PROPERTY_RESOLVER_CACHE.get(cls);
if (map != null) {
WeakReference member = map.get(property);
if (member != null) return member.get();
}
return null;
}
public static Class[] checkParmTypesCache(Method member) {
WeakReference 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(ret = member.getParameterTypes()));
}
return ret;
}
private static void addMethodCache(Class cls, Integer property, Method member) {
synchronized (METHOD_RESOLVER_CACHE) {
WeakHashMap> map = METHOD_RESOLVER_CACHE.get(cls);
if (map == null) {
METHOD_RESOLVER_CACHE.put(cls, map = new WeakHashMap>());
}
map.put(property, new WeakReference