org.jsimpledb.parse.expr.BaseExprParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jsimpledb-parse Show documentation
Show all versions of jsimpledb-parse Show documentation
JSimpleDB classes for parsing Java expressions.
The newest version!
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package org.jsimpledb.parse.expr;
import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import org.jsimpledb.JField;
import org.jsimpledb.JObject;
import org.jsimpledb.JSimpleField;
import org.jsimpledb.core.ObjId;
import org.jsimpledb.core.SimpleField;
import org.jsimpledb.parse.ParseException;
import org.jsimpledb.parse.ParseSession;
import org.jsimpledb.parse.ParseUtil;
import org.jsimpledb.parse.Parser;
import org.jsimpledb.parse.SpaceParser;
import org.jsimpledb.util.ParseContext;
/**
* Parses basic left-associative Java expressions such as auto-increment expressions, array access, field access, invocation, etc.
*
*
* Includes these extensions:
*
* - Access to Java bean methods via "property syntax", e.g., {@code foo.name = "fred"} means {@code foo.setName("fred")}
* - Access database object fields via "property syntax" given an object ID, e.g.,
@fc21bf6d8930a215.name
* - Array syntax for {@link java.util.Map} and {@link java.util.List} value access, e.g., {@code mymap[key] = value} means
* {@code mymap.put(key, value)}, and {@code mylist[12] = "abc"} means {@code mylist.set(12, "abc")}
*
*/
public class BaseExprParser implements Parser {
public static final BaseExprParser INSTANCE = new BaseExprParser();
private final SpaceParser spaceParser = new SpaceParser();
@Override
public Node parse(ParseSession session, ParseContext ctx, boolean complete) {
// Parse initial atom
Node node = AtomExprParser.INSTANCE.parse(session, ctx, complete);
// Repeatedly parse post-operators, giving left-to-right association
// These are: foo[x], foo::x, foo.x(), foo.x, foo++, foo--
while (true) {
// Skip whitespace
ctx.skipWhitespace();
// Handle array reference
if (ctx.tryLiteral("[")) {
this.spaceParser.parse(ctx, complete);
final Node index = ExprParser.INSTANCE.parse(session, ctx, complete);
this.spaceParser.parse(ctx, complete);
if (!ctx.tryLiteral("]"))
throw new ParseException(ctx).addCompletion("]");
final Node target = node;
node = new Node() {
@Override
public Value evaluate(ParseSession session) {
return Op.ARRAY_ACCESS.apply(session, target.evaluate(session), index.evaluate(session));
}
@Override
public Class> getType(ParseSession session) {
final Class> arrayType = target.getType(session);
return arrayType.isArray() ? arrayType.getComponentType() : Object.class;
}
};
continue;
}
// Handle bound method reference
if (ctx.tryLiteral("::")) {
ctx.skipWhitespace();
final Matcher methodMatch = ctx.tryPattern(ParseUtil.IDENT_PATTERN);
if (methodMatch == null)
throw new ParseException(ctx, "expected method name");
node = new BoundMethodReferenceNode(node, methodMatch.group());
continue;
}
// Support tab-completion for ".foo", based on the type of
if (complete) {
final Matcher propertyPrefixMatch = ctx.tryPattern("\\.\\s*(" + ParseUtil.IDENT_PATTERN + ")?$");
if (propertyPrefixMatch != null) {
String prefix = propertyPrefixMatch.group(1);
if (prefix == null)
prefix = "";
throw new ParseException(ctx)
.addCompletions(AtomExprParser.getInstanceMemberCompletions(node.getType(session), prefix));
}
}
// Handle instance method invocation
final Matcher invokeMatch = ctx.tryPattern("\\.\\s*(" + ParseUtil.IDENT_PATTERN + ")\\s*\\(");
if (invokeMatch != null) {
node = new MethodInvokeNode(node, invokeMatch.group(1), AtomExprParser.parseParams(session, ctx, complete));
continue;
}
// Handle property/field reference
final Matcher propertyMatch = ctx.tryPattern("\\.\\s*(" + ParseUtil.IDENT_PATTERN + ")\\s*");
if (propertyMatch != null) {
final String propertyName = propertyMatch.group(1);
final Node target = node;
node = new Node() {
@Override
public Value evaluate(ParseSession session) {
return BaseExprParser.this.evaluateProperty(session, target.evaluate(session), propertyName);
}
@Override
public Class> getType(ParseSession session) {
return BaseExprParser.this.getPropertyType(session, target, propertyName);
}
};
continue;
}
// Handle post-increment
if (ctx.tryLiteral("++")) {
node = this.createPostcrementNode("increment", node, true);
continue;
}
// Handle post-decrement
if (ctx.tryLiteral("--")) {
node = this.createPostcrementNode("decrement", node, false);
continue;
}
// We're done
break;
}
// Done
return node;
}
private Value evaluateProperty(ParseSession session, Value value, final String name) {
// Evaluate target
final Object target = value.checkNotNull(session, "property `" + name + "' access");
final Class> cl = target.getClass();
// Handle properties of database objects (i.e., database fields)
if (session.getMode().hasJSimpleDB() && target instanceof JObject) {
// Get object and ID
final JObject jobj = (JObject)target;
final ObjId id = jobj.getObjId();
// Resolve JField
JField jfield0;
try {
jfield0 = ParseUtil.resolveJField(session, id, name);
} catch (IllegalArgumentException e) {
jfield0 = null;
}
final JField jfield = jfield0;
// Return value reflecting the field if the field was found
if (jfield instanceof JSimpleField)
return new JSimpleFieldValue(jobj, (JSimpleField)jfield);
else if (jfield != null)
return new JFieldValue(jobj, jfield);
} else if (session.getMode().hasCoreAPI() && target instanceof ObjId) {
final ObjId id = (ObjId)target;
// Resolve field
org.jsimpledb.core.Field> field0;
try {
field0 = ParseUtil.resolveField(session, id, name);
} catch (IllegalArgumentException e) {
field0 = null;
}
final org.jsimpledb.core.Field> field = field0;
// Return value reflecting the field if the field was found
if (field instanceof SimpleField)
return new SimpleFieldValue(id, (SimpleField>)field);
else if (field != null)
return new FieldValue(id, field);
}
// Try bean property accessed via bean methods
final BeanInfo beanInfo;
try {
beanInfo = Introspector.getBeanInfo(cl);
} catch (IntrospectionException e) {
throw new EvalException("error introspecting class `" + cl.getName() + "': " + e, e);
}
for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
if (propertyDescriptor instanceof IndexedPropertyDescriptor)
continue;
if (!propertyDescriptor.getName().equals(name))
continue;
final Method getter = propertyDescriptor.getReadMethod() != null ?
MethodUtil.makeAccessible(propertyDescriptor.getReadMethod()) : null;
final Method setter = propertyDescriptor.getWriteMethod() != null ?
MethodUtil.makeAccessible(propertyDescriptor.getWriteMethod()) : null;
if (getter != null && setter != null)
return new MutableBeanPropertyValue(target, propertyDescriptor.getName(), getter, setter);
else if (getter != null)
return new BeanPropertyValue(target, propertyDescriptor.getName(), getter);
}
// Try instance field
/*final*/ Field javaField;
try {
javaField = AtomExprParser.findField(cl, name, false);
} catch (NoSuchFieldException e) {
javaField = null;
}
if (javaField != null)
return new InstanceFieldValue(target, javaField);
// Try array.length
if (target.getClass().isArray() && name.equals("length"))
return new ConstValue(Array.getLength(target));
// Not found
throw new EvalException("property `" + name + "' not found in " + cl);
}
private Class> getPropertyType(ParseSession session, Node node, final String name) {
// Try bean property accessed via bean methods
final Class> cl = node.getType(session);
final BeanInfo beanInfo;
try {
beanInfo = Introspector.getBeanInfo(cl);
} catch (IntrospectionException e) {
return Object.class;
}
for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
if (propertyDescriptor instanceof IndexedPropertyDescriptor)
continue;
if (!propertyDescriptor.getName().equals(name))
continue;
if (propertyDescriptor.getReadMethod() == null)
continue;
return propertyDescriptor.getReadMethod().getReturnType();
}
// Try instance field
/*final*/ Field javaField;
try {
javaField = AtomExprParser.findField(cl, name, false);
} catch (NoSuchFieldException e) {
javaField = null;
}
if (javaField != null)
return javaField.getType();
// Try array.length
if (cl.isArray() && name.equals("length"))
return Integer.class;
// Dunno
return Object.class;
}
private Node createPostcrementNode(final String operation, final Node node, final boolean increment) {
return new Node() {
@Override
public Value evaluate(ParseSession session) {
return node.evaluate(session).xxcrement(session, "post-" + operation, increment);
}
@Override
public Class> getType(ParseSession session) {
return node.getType(session);
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy