|
|
@@ -6,120 +6,113 @@
|
|
|
*/
|
|
|
package net.ranides.assira.reflection;
|
|
|
|
|
|
+import java.io.Serializable;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.regex.Matcher;
|
|
|
import java.util.regex.Pattern;
|
|
|
import net.ranides.assira.collection.ArrayUtils;
|
|
|
+import net.ranides.assira.text.Strings;
|
|
|
|
|
|
/**
|
|
|
- * Klasa do wyciągania z obiektów wartości na podstawie wyrażenia
|
|
|
- * pseudo-javowego.
|
|
|
- * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * Compiled expression usable for read/write value from objects.
|
|
|
+ * You can access recursively properties from properties.
|
|
|
+ *
|
|
|
+ * <h3>Supported syntax:</h3>
|
|
|
+ * <table>
|
|
|
+ * <tr><td>.name</td><td>read field or bean-property from object</td></tr>
|
|
|
+ * <tr><td>name</td><td>read field or bean-property from object</td></tr>
|
|
|
+ * <tr><td>#name</td><td>get key from a {@link Map}</td></tr>
|
|
|
+ * <tr><td>[number]</td><td>get item from an array-like object</td></tr>
|
|
|
+ * <tr><td>number</td><td>get item from an array-like object</td></tr>
|
|
|
+ * </table>
|
|
|
+ *
|
|
|
+ * <h3>Supported array-like objects:</h3>
|
|
|
+ * <ul>
|
|
|
+ * <li>{@link List}</li>
|
|
|
+ * <li>array</li>
|
|
|
+ * <li>{@link Matcher}</li>
|
|
|
+ * </li>
|
|
|
+ *
|
|
|
+ * <h3>Example:</h3>
|
|
|
+ * <pre>
|
|
|
+ * object.property.list[4].users#id.name
|
|
|
+ * </pre>
|
|
|
+ *
|
|
|
*/
|
|
|
-public final class Resolver {
|
|
|
+public final class Resolver implements Serializable {
|
|
|
+
|
|
|
+ private static final long serialVersionUID = 1L;
|
|
|
|
|
|
private static final Pattern RE_RESOLVE = Pattern.compile(
|
|
|
"(?:\\G\\.?([^\\.\\[\\]#]+))|(?:\\G#([^\\.\\[\\]#]+))|(?:\\G\\[([0-9]+)\\])"
|
|
|
);
|
|
|
|
|
|
- private Resolver() {
|
|
|
- // utility class
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Returns value of variable resolved by expression.
|
|
|
- * You can access recursively properties from properties. Supported syntax:
|
|
|
- * <table>
|
|
|
- * <tr><td>.name</td><td>read field or bean-property from object</td></tr>
|
|
|
- * <tr><td>name</td><td>read field or bean-property from object</td></tr>
|
|
|
- * <tr><td>#name</td><td>get key from a {@link Map}</td></tr>
|
|
|
- * <tr><td>[number]</td><td>get item from a {@link List} or array</td></tr>
|
|
|
- * <tr><td>number</td><td>get item from a {@link List} or array</td></tr>
|
|
|
- * </table>
|
|
|
- * <pre>
|
|
|
- * object.property.list[4].users#id.name
|
|
|
- * </pre>
|
|
|
- * @param object
|
|
|
- * @param path
|
|
|
- * @return
|
|
|
- */
|
|
|
- public static Object get(Object object, String path) {
|
|
|
- return resolve(object, path, false, null);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Changes value of variable resolved by expression.
|
|
|
- * You can access recursively properties from properties. Supported syntax:
|
|
|
- * <table>
|
|
|
- * <tr><td>.name</td><td>read field or bean-property from object</td></tr>
|
|
|
- * <tr><td>name</td><td>read field or bean-property from object</td></tr>
|
|
|
- * <tr><td>#name</td><td>get key from a {@link Map}</td></tr>
|
|
|
- * <tr><td>[number]</td><td>get item from a {@link List} or array</td></tr>
|
|
|
- * <tr><td>number</td><td>get item from a {@link List} or array</td></tr>
|
|
|
- * </table>
|
|
|
- * <pre>
|
|
|
- * object.property.list[4].users#id.name
|
|
|
- * </pre>
|
|
|
- *
|
|
|
- * @param object
|
|
|
- * @param path
|
|
|
- * @param value
|
|
|
- * @return
|
|
|
- */
|
|
|
- public static Object set(Object object, String path, Object value) {
|
|
|
- return resolve(object, path, true, value);
|
|
|
- }
|
|
|
-
|
|
|
- private static Object resolve(Object object, String path, boolean setmode, Object value) {
|
|
|
- Object context = object;
|
|
|
- Matcher hit = RE_RESOLVE.matcher(path);
|
|
|
+ private final String expression;
|
|
|
+ private final Token[] tokens;
|
|
|
+
|
|
|
+ private Resolver(String expression) {
|
|
|
+ this.expression = expression;
|
|
|
+
|
|
|
+ ArrayList<Token> list = new ArrayList<>();
|
|
|
+ Matcher hit = RE_RESOLVE.matcher(expression);
|
|
|
while(hit.find()) {
|
|
|
- boolean last = (path.length() == hit.end());
|
|
|
-
|
|
|
- if(last && setmode) {
|
|
|
- Resolver.set(context, hit, value);
|
|
|
- return value;
|
|
|
- }
|
|
|
- context = Resolver.get(context, hit);
|
|
|
- if(last && !setmode) {
|
|
|
- return context;
|
|
|
+ list.add(compileToken(hit));
|
|
|
+ if(expression.length() == hit.end()) {
|
|
|
+ tokens = list.toArray(new Token[list.size()]);
|
|
|
+ return;
|
|
|
}
|
|
|
}
|
|
|
throw new IllegalArgumentException("Invalid expression");
|
|
|
}
|
|
|
-
|
|
|
- private static Object get(Object context, Matcher hit) throws NumberFormatException {
|
|
|
- if( isint(hit.group(1)) ) {
|
|
|
- return ArrayUtils.wrap(context).get( Integer.parseInt(hit.group(1)) );
|
|
|
- }
|
|
|
- if( null != hit.group(1) ) {
|
|
|
- return BeanModel.fromObject(context).get(context, hit.group(1));
|
|
|
- }
|
|
|
- if( null != hit.group(2) ) {
|
|
|
- return ((Map<?,?>)context).get(hit.group(2));
|
|
|
+
|
|
|
+ public static Resolver compile(String expression) {
|
|
|
+ return new Resolver(expression);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Object get(Object object, String expression) {
|
|
|
+ return Resolver.compile(expression).get(object);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Object set(Object object, String expression, Object value) {
|
|
|
+ return Resolver.compile(expression).set(object, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Object get(Object object) {
|
|
|
+ Object context = object;
|
|
|
+ for(Token token : tokens) {
|
|
|
+ context = token.get(context);
|
|
|
}
|
|
|
- if( null != hit.group(3) ) {
|
|
|
- return ArrayUtils.wrap(context).get( Integer.parseInt(hit.group(3)) );
|
|
|
+ return context;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Object set(Object object, Object value) {
|
|
|
+ Object context = object;
|
|
|
+ int n = tokens.length-1;
|
|
|
+ for(int i=0; i<n; i++) {
|
|
|
+ context = tokens[i].get(context);
|
|
|
}
|
|
|
- throw new IllegalArgumentException("Invalid expression fragment:" + hit.group());
|
|
|
+ tokens[n].set(context, value);
|
|
|
+ return context;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String expression() {
|
|
|
+ return expression;
|
|
|
}
|
|
|
|
|
|
- private static void set(Object context, Matcher hit, Object value) throws NumberFormatException {
|
|
|
+ private static Token compileToken(Matcher hit) throws NumberFormatException {
|
|
|
if( isint(hit.group(1)) ) {
|
|
|
- ArrayUtils.wrap(context).set(Integer.parseInt(hit.group(1)), value);
|
|
|
- return;
|
|
|
+ return new AToken(hit.group(1));
|
|
|
}
|
|
|
if( null != hit.group(1) ) {
|
|
|
- BeanModel.fromObject(context).put(context, hit.group(1), value);
|
|
|
- return;
|
|
|
+ return new BToken(hit.group(1));
|
|
|
}
|
|
|
if( null != hit.group(2) ) {
|
|
|
- ((Map<String,Object>)context).put(hit.group(2),value);
|
|
|
- return;
|
|
|
+ return new MToken(hit.group(2));
|
|
|
}
|
|
|
if( null != hit.group(3) ) {
|
|
|
- ArrayUtils.wrap(context).set(Integer.parseInt(hit.group(3)), value);
|
|
|
- return;
|
|
|
+ return new AToken(hit.group(3));
|
|
|
}
|
|
|
throw new IllegalArgumentException("Invalid expression fragment:" + hit.group());
|
|
|
}
|
|
|
@@ -136,4 +129,116 @@ public final class Resolver {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public int hashCode() {
|
|
|
+ return expression.hashCode();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean equals(Object object) {
|
|
|
+ if(!(object instanceof Resolver)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Resolver other = (Resolver)object;
|
|
|
+ return Strings.isEqual(this.expression, other.expression);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object writeReplace() {
|
|
|
+ return new SerializationProxy(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class SerializationProxy implements Serializable {
|
|
|
+
|
|
|
+ String expression;
|
|
|
+
|
|
|
+ public SerializationProxy(Resolver source) {
|
|
|
+ this.expression = source.expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object readResolve() {
|
|
|
+ return new Resolver(expression);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private interface Token {
|
|
|
+
|
|
|
+ Object get(Object context);
|
|
|
+
|
|
|
+ void set(Object context, Object value);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class AToken implements Token {
|
|
|
+
|
|
|
+ private final int index;
|
|
|
+
|
|
|
+ public AToken(String index) {
|
|
|
+ this.index = Integer.parseInt(index);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object get(Object context) {
|
|
|
+ if(context instanceof Matcher) {
|
|
|
+ return ((Matcher)context).group(index);
|
|
|
+ }
|
|
|
+ if(context instanceof CharSequence) {
|
|
|
+ return ((CharSequence)context).charAt(index);
|
|
|
+ }
|
|
|
+ return ArrayUtils.wrap(context).get(index);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void set(Object context, Object value) {
|
|
|
+ if(context instanceof Matcher) {
|
|
|
+ throw new UnsupportedOperationException("Can't modify object: Matcher");
|
|
|
+ }
|
|
|
+ if(context instanceof CharSequence) {
|
|
|
+ throw new UnsupportedOperationException("Can't modify object: CharSequence");
|
|
|
+ }
|
|
|
+ ArrayUtils.wrap(context).set(index, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class BToken implements Token {
|
|
|
+
|
|
|
+ private final String index;
|
|
|
+
|
|
|
+ public BToken(String index) {
|
|
|
+ this.index = index;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object get(Object context) {
|
|
|
+ return BeanModel.fromObject(context).get(context, index);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void set(Object context, Object value) {
|
|
|
+ BeanModel.fromObject(context).put(context, index, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class MToken implements Token {
|
|
|
+
|
|
|
+ private final String key;
|
|
|
+
|
|
|
+ public MToken(String key) {
|
|
|
+ this.key = key;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object get(Object context) {
|
|
|
+ return ((Map<?,?>)context).get(key);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void set(Object context, Object value) {
|
|
|
+ ((Map<String,Object>)context).put(key, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
}
|