org.mvel2.PropertyAccessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tbel Show documentation
Show all versions of tbel Show documentation
TBEL is a powerful expression language for ThingsBoard platform user-defined functions.
Original implementation is based on MVEL.
/**
* 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>> READ_PROPERTY_RESOLVER_CACHE;
private static final Map>> WRITE_PROPERTY_RESOLVER_CACHE;
private static final Map>> METHOD_RESOLVER_CACHE;
private static final Map> METHOD_PARMTYPES_CACHE;
static {
READ_PROPERTY_RESOLVER_CACHE = Collections.synchronizedMap(new WeakHashMap>>(10));
WRITE_PROPERTY_RESOLVER_CACHE = Collections.synchronizedMap(new WeakHashMap>>(10));
METHOD_RESOLVER_CACHE = Collections.synchronizedMap(new WeakHashMap>>(10));
METHOD_PARMTYPES_CACHE = Collections.synchronizedMap(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, 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> 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