|
|
@@ -1,383 +1,382 @@
|
|
|
-/*
|
|
|
- * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
- * @copyright Ranides Atterwim
|
|
|
- * @license WTFPL
|
|
|
- * @url http://ranides.net/projects/assira
|
|
|
- */
|
|
|
-package net.ranides.assira.reflection.inspect;
|
|
|
-
|
|
|
-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.generic.Serializer;
|
|
|
-import net.ranides.assira.generic.ValueUtils;
|
|
|
-import net.ranides.assira.generic.Wrapper;
|
|
|
-import net.ranides.assira.reflection.GenericClass;
|
|
|
-import net.ranides.assira.reflection.TypeFactory;
|
|
|
-import net.ranides.assira.text.LexicalCast;
|
|
|
-import net.ranides.assira.text.Strings;
|
|
|
-
|
|
|
-/**
|
|
|
- * 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>
|
|
|
- *
|
|
|
- * @todo (migration) assira (reflection)
|
|
|
- */
|
|
|
-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 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()) {
|
|
|
- list.add(compileToken(hit));
|
|
|
- if(expression.length() == hit.end()) {
|
|
|
- tokens = list.toArray(new Token[list.size()]);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- throw new IllegalArgumentException("Invalid expression");
|
|
|
- }
|
|
|
-
|
|
|
- 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 void set(Object object, String expression, Object value) {
|
|
|
- Resolver.compile(expression).set(object, value);
|
|
|
- }
|
|
|
-
|
|
|
- public static Wrapper<Object> reference(Object object, String expression) {
|
|
|
- return Resolver.compile(expression).reference(object);
|
|
|
- }
|
|
|
-
|
|
|
- public Object get(Object object) {
|
|
|
- Object context = object;
|
|
|
- for(Token token : tokens) {
|
|
|
- context = token.get(context);
|
|
|
- }
|
|
|
- return context;
|
|
|
- }
|
|
|
-
|
|
|
- public void 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);
|
|
|
- }
|
|
|
- tokens[n].set(context, value);
|
|
|
- }
|
|
|
-
|
|
|
- public Wrapper<Object> reference(Object object) {
|
|
|
- TypeContext context = new TypeContext().bind(object);
|
|
|
- int n = tokens.length-1;
|
|
|
- for(int i=0; i<n; i++) {
|
|
|
- context = tokens[i].typeof(context);
|
|
|
- }
|
|
|
- GenericClass type = tokens[n].typeof(context).type;
|
|
|
- return new RWrapper(context.value, type, tokens[n]);
|
|
|
- }
|
|
|
-
|
|
|
- private static class RWrapper extends Wrapper<Object> {
|
|
|
-
|
|
|
- private final Object context;
|
|
|
- private final GenericClass type;
|
|
|
- private final Token token;
|
|
|
-
|
|
|
- RWrapper(Object context, GenericClass type, Token token) {
|
|
|
- this.context = context;
|
|
|
- this.type = type;
|
|
|
- this.token = token;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public GenericClass type() {
|
|
|
- return type;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Object get() {
|
|
|
- return token.get(context);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void set(Object value) {
|
|
|
- token.set(context, value);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void put(Object value) {
|
|
|
- token.set(context, LexicalCast.cast(value, type));
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- public String expression() {
|
|
|
- return expression;
|
|
|
- }
|
|
|
-
|
|
|
- private static Token compileToken(Matcher hit) throws NumberFormatException {
|
|
|
- if( isint(hit.group(1)) ) {
|
|
|
- return new AToken(hit.group(1));
|
|
|
- }
|
|
|
- if( null != hit.group(1) ) {
|
|
|
- return new BToken(hit.group(1));
|
|
|
- }
|
|
|
- if( null != hit.group(2) ) {
|
|
|
- return new MToken(hit.group(2));
|
|
|
- }
|
|
|
- if( null != hit.group(3) ) {
|
|
|
- return new AToken(hit.group(3));
|
|
|
- }
|
|
|
- throw new IllegalArgumentException("Invalid expression fragment:" + hit.group());
|
|
|
- }
|
|
|
-
|
|
|
- private static boolean isint(String value) {
|
|
|
- if(null == value) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- for(int i=0, n=value.length(); i<n; i++) {
|
|
|
- if( !Character.isDigit(value.charAt(i)) ) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
- 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 Serializer.proxy(this, expression);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- private interface Token {
|
|
|
-
|
|
|
- Object get(Object context);
|
|
|
-
|
|
|
- void set(Object context, Object value);
|
|
|
-
|
|
|
- TypeContext typeof(TypeContext context);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- 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 ValueUtils.or(((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);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public TypeContext typeof(TypeContext context) {
|
|
|
- if(context.is(Matcher.class)) {
|
|
|
- TypeContext ret = new TypeContext(String.class);
|
|
|
- if(null != context.value) {
|
|
|
- ret.bind( ValueUtils.or(((Matcher)context.value).group(index), "") );
|
|
|
- }
|
|
|
- return ret;
|
|
|
- }
|
|
|
- if(context.is(CharSequence.class)) {
|
|
|
- TypeContext ret = new TypeContext(char.class);
|
|
|
- if(null != context.value) {
|
|
|
- ret.bind(((CharSequence)context.value).charAt(index));
|
|
|
- }
|
|
|
- return ret;
|
|
|
- }
|
|
|
- if(context.type.rawType().isArray()) {
|
|
|
- TypeContext ret = new TypeContext(context.type.rawType().getComponentType());
|
|
|
- if(null != context.value) {
|
|
|
- ret.bind( ArrayUtils.wrap(context.value).get(index) );
|
|
|
- }
|
|
|
- return ret;
|
|
|
- }
|
|
|
- if(context.value instanceof List) {
|
|
|
- TypeContext ret = new TypeContext(context.type.params()[0]);
|
|
|
- if(null != context.value) {
|
|
|
- ret.bind( ArrayUtils.wrap(context.value).get(index) );
|
|
|
- }
|
|
|
- return ret;
|
|
|
- }
|
|
|
- throw new IllegalArgumentException("Invalid argument, type is not an array: " + context);
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private static class BToken implements Token {
|
|
|
-
|
|
|
- private final String index;
|
|
|
-
|
|
|
- public BToken(String index) {
|
|
|
- this.index = index;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Object get(Object context) {
|
|
|
- return BeanInspector.forObject(context).get(context, index);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void set(Object context, Object value) {
|
|
|
- BeanInspector.forObject(context).put(context, index, value);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public TypeContext typeof(TypeContext context) {
|
|
|
- if( null == context.value ) {
|
|
|
- BeanInspector model = BeanInspector.forClass(context.type.rawType());
|
|
|
- return new TypeContext(model.typeof(index));
|
|
|
- } else {
|
|
|
- BeanInspector model = BeanInspector.forObject(context.value);
|
|
|
- return new TypeContext(model.typeof(index)).bind(model.get(context.value, index));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public TypeContext typeof(TypeContext context) {
|
|
|
- if( null == context.value) {
|
|
|
- return new TypeContext( context.type.params()[1]);
|
|
|
- } else {
|
|
|
- return new TypeContext( context.type.params()[1])
|
|
|
- .bind(((Map<?,?>)context.value).get(key));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private static class TypeContext {
|
|
|
-
|
|
|
- private GenericClass type;
|
|
|
- private Object value;
|
|
|
-
|
|
|
- public TypeContext() {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- public TypeContext(Class<?> type) {
|
|
|
- this(TypeFactory.construct(type));
|
|
|
- }
|
|
|
-
|
|
|
- public TypeContext(GenericClass type) {
|
|
|
- this.type = type;
|
|
|
- this.value = null;
|
|
|
- }
|
|
|
-
|
|
|
- public boolean is(Class<?> type) {
|
|
|
- if(value != null && type.isInstance(value)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- return type.isAssignableFrom(this.type.rawType());
|
|
|
- }
|
|
|
-
|
|
|
- public TypeContext bind(Object value) {
|
|
|
- this.value = value;
|
|
|
- if( null == value ) {
|
|
|
- return this;
|
|
|
- }
|
|
|
- if((null == type) || (!type.isParametrised() && !type.rawType().isPrimitive()) ) {
|
|
|
- this.type = TypeFactory.construct(value.getClass());
|
|
|
- }
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public String toString() {
|
|
|
- return "TypeContext{" + "type=" + type + ", value=" + value + '}';
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-}
|
|
|
+/*
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
+ */
|
|
|
+package net.ranides.assira.reflection.inspect;
|
|
|
+
|
|
|
+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.generic.Serializer;
|
|
|
+import net.ranides.assira.generic.ValueUtils;
|
|
|
+import net.ranides.assira.generic.Wrapper;
|
|
|
+import net.ranides.assira.reflection.GenericClass;
|
|
|
+import net.ranides.assira.reflection.TypeFactory;
|
|
|
+import net.ranides.assira.text.LexicalCast;
|
|
|
+import net.ranides.assira.text.Strings;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 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 implements Serializable {
|
|
|
+
|
|
|
+ private static final long serialVersionUID = 1L;
|
|
|
+
|
|
|
+ private static final Pattern RE_RESOLVE = Pattern.compile(
|
|
|
+ "(?:\\G\\.?([^\\.\\[\\]#]+))|(?:\\G#([^\\.\\[\\]#]+))|(?:\\G\\[([0-9]+)\\])"
|
|
|
+ );
|
|
|
+
|
|
|
+ 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()) {
|
|
|
+ list.add(compileToken(hit));
|
|
|
+ if(expression.length() == hit.end()) {
|
|
|
+ tokens = list.toArray(new Token[list.size()]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw new IllegalArgumentException("Invalid expression");
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 void set(Object object, String expression, Object value) {
|
|
|
+ Resolver.compile(expression).set(object, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Wrapper<Object> reference(Object object, String expression) {
|
|
|
+ return Resolver.compile(expression).reference(object);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Object get(Object object) {
|
|
|
+ Object context = object;
|
|
|
+ for(Token token : tokens) {
|
|
|
+ context = token.get(context);
|
|
|
+ }
|
|
|
+ return context;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void 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);
|
|
|
+ }
|
|
|
+ tokens[n].set(context, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Wrapper<Object> reference(Object object) {
|
|
|
+ TypeContext context = new TypeContext().bind(object);
|
|
|
+ int n = tokens.length-1;
|
|
|
+ for(int i=0; i<n; i++) {
|
|
|
+ context = tokens[i].typeof(context);
|
|
|
+ }
|
|
|
+ GenericClass type = tokens[n].typeof(context).type;
|
|
|
+ return new RWrapper(context.value, type, tokens[n]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class RWrapper extends Wrapper<Object> {
|
|
|
+
|
|
|
+ private final Object context;
|
|
|
+ private final GenericClass type;
|
|
|
+ private final Token token;
|
|
|
+
|
|
|
+ RWrapper(Object context, GenericClass type, Token token) {
|
|
|
+ this.context = context;
|
|
|
+ this.type = type;
|
|
|
+ this.token = token;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public GenericClass type() {
|
|
|
+ return type;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object get() {
|
|
|
+ return token.get(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void set(Object value) {
|
|
|
+ token.set(context, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void put(Object value) {
|
|
|
+ token.set(context, LexicalCast.cast(value, type));
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public String expression() {
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Token compileToken(Matcher hit) throws NumberFormatException {
|
|
|
+ if( isint(hit.group(1)) ) {
|
|
|
+ return new AToken(hit.group(1));
|
|
|
+ }
|
|
|
+ if( null != hit.group(1) ) {
|
|
|
+ return new BToken(hit.group(1));
|
|
|
+ }
|
|
|
+ if( null != hit.group(2) ) {
|
|
|
+ return new MToken(hit.group(2));
|
|
|
+ }
|
|
|
+ if( null != hit.group(3) ) {
|
|
|
+ return new AToken(hit.group(3));
|
|
|
+ }
|
|
|
+ throw new IllegalArgumentException("Invalid expression fragment:" + hit.group());
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean isint(String value) {
|
|
|
+ if(null == value) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ for(int i=0, n=value.length(); i<n; i++) {
|
|
|
+ if( !Character.isDigit(value.charAt(i)) ) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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 Serializer.proxy(this, expression);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private interface Token {
|
|
|
+
|
|
|
+ Object get(Object context);
|
|
|
+
|
|
|
+ void set(Object context, Object value);
|
|
|
+
|
|
|
+ TypeContext typeof(TypeContext context);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 ValueUtils.or(((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);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public TypeContext typeof(TypeContext context) {
|
|
|
+ if(context.is(Matcher.class)) {
|
|
|
+ TypeContext ret = new TypeContext(String.class);
|
|
|
+ if(null != context.value) {
|
|
|
+ ret.bind( ValueUtils.or(((Matcher)context.value).group(index), "") );
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ if(context.is(CharSequence.class)) {
|
|
|
+ TypeContext ret = new TypeContext(char.class);
|
|
|
+ if(null != context.value) {
|
|
|
+ ret.bind(((CharSequence)context.value).charAt(index));
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ if(context.type.rawType().isArray()) {
|
|
|
+ TypeContext ret = new TypeContext(context.type.rawType().getComponentType());
|
|
|
+ if(null != context.value) {
|
|
|
+ ret.bind( ArrayUtils.wrap(context.value).get(index) );
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ if(context.value instanceof List) {
|
|
|
+ TypeContext ret = new TypeContext(context.type.params()[0]);
|
|
|
+ if(null != context.value) {
|
|
|
+ ret.bind( ArrayUtils.wrap(context.value).get(index) );
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ throw new IllegalArgumentException("Invalid argument, type is not an array: " + context);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class BToken implements Token {
|
|
|
+
|
|
|
+ private final String index;
|
|
|
+
|
|
|
+ public BToken(String index) {
|
|
|
+ this.index = index;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Object get(Object context) {
|
|
|
+ return BeanInspector.forObject(context).get(context, index);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void set(Object context, Object value) {
|
|
|
+ BeanInspector.forObject(context).put(context, index, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public TypeContext typeof(TypeContext context) {
|
|
|
+ if( null == context.value ) {
|
|
|
+ BeanInspector model = BeanInspector.forClass(context.type.rawType());
|
|
|
+ return new TypeContext(model.typeof(index));
|
|
|
+ } else {
|
|
|
+ BeanInspector model = BeanInspector.forObject(context.value);
|
|
|
+ return new TypeContext(model.typeof(index)).bind(model.get(context.value, index));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public TypeContext typeof(TypeContext context) {
|
|
|
+ if( null == context.value) {
|
|
|
+ return new TypeContext( context.type.params()[1]);
|
|
|
+ } else {
|
|
|
+ return new TypeContext( context.type.params()[1])
|
|
|
+ .bind(((Map<?,?>)context.value).get(key));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class TypeContext {
|
|
|
+
|
|
|
+ private GenericClass type;
|
|
|
+ private Object value;
|
|
|
+
|
|
|
+ public TypeContext() {
|
|
|
+ // do nothing
|
|
|
+ }
|
|
|
+
|
|
|
+ public TypeContext(Class<?> type) {
|
|
|
+ this(TypeFactory.construct(type));
|
|
|
+ }
|
|
|
+
|
|
|
+ public TypeContext(GenericClass type) {
|
|
|
+ this.type = type;
|
|
|
+ this.value = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean is(Class<?> type) {
|
|
|
+ if(value != null && type.isInstance(value)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return type.isAssignableFrom(this.type.rawType());
|
|
|
+ }
|
|
|
+
|
|
|
+ public TypeContext bind(Object value) {
|
|
|
+ this.value = value;
|
|
|
+ if( null == value ) {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ if((null == type) || (!type.isParametrised() && !type.rawType().isPrimitive()) ) {
|
|
|
+ this.type = TypeFactory.construct(value.getClass());
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ return "TypeContext{" + "type=" + type + ", value=" + value + '}';
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+}
|