|
|
@@ -12,24 +12,115 @@ import java.util.Map;
|
|
|
import java.util.Optional;
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
|
+/**
|
|
|
+ * ResolvePattern can handle object of different types in different way.
|
|
|
+ * It delegates work to different, specialized ResolveModel instances.
|
|
|
+ *
|
|
|
+ * For example, we can write ResolveModel for array inspection, for map inspection,
|
|
|
+ * or for your own custom object.
|
|
|
+ *
|
|
|
+ * It is responsibility of ResolveModel itself to tell if some object can be
|
|
|
+ * handled by it. ResolvePattern makes tests against object using all provided
|
|
|
+ * ResolveModels from ResolveConfiguration and selects first one which told,
|
|
|
+ * that it can process the object.
|
|
|
+ */
|
|
|
public abstract class ResolveModel extends SerializableCode {
|
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
|
|
private static final String ARRAY_LENGTH = "length";
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns true, if model is able to deduce type of field declared by "that" type
|
|
|
+ *
|
|
|
+ * Please remember that type deduction works without instance objects.
|
|
|
+ *
|
|
|
+ * You must return "true" in exactly the same situations as "matches",
|
|
|
+ * because obviously you are the one who knows type of value returned by "get"
|
|
|
+ *
|
|
|
+ * @param that that
|
|
|
+ * @param name name
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
public abstract boolean matchesType(IClass<?> that, String name);
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns true, if model is able to read field form "that" object
|
|
|
+ *
|
|
|
+ * You must return "true" in exactly the same situations as "matchesType",
|
|
|
+ * because obviously you are the one who knows type of value returned by "get"
|
|
|
+ *
|
|
|
+ * @param that that
|
|
|
+ * @param name name
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
public abstract boolean matches(Object that, String name);
|
|
|
|
|
|
+ /**
|
|
|
+ * Reads value of property from "that" object.
|
|
|
+ *
|
|
|
+ * If property does not exist, you shouldn't throw exception, but
|
|
|
+ * you should change "status.error" flag and return null.
|
|
|
+ *
|
|
|
+ * @param status (output) status
|
|
|
+ * @param that that
|
|
|
+ * @param name name
|
|
|
+ * @return Object
|
|
|
+ */
|
|
|
public abstract Object get(ResolveStatus status, Object that, String name);
|
|
|
|
|
|
+ /**
|
|
|
+ * Changes value of property from "that" object.
|
|
|
+ *
|
|
|
+ * If property does not exist, you shouldn't throw exception, but
|
|
|
+ * you should change "status.error" flag and do nothing.
|
|
|
+ *
|
|
|
+ * @param status (output) status
|
|
|
+ * @param that that
|
|
|
+ * @param name name
|
|
|
+ * @param value value
|
|
|
+ */
|
|
|
public abstract void set(ResolveStatus status, Object that, String name, Object value);
|
|
|
|
|
|
+ /**
|
|
|
+ * Changes value of property from "that" object.
|
|
|
+ * Returns previous value of property.
|
|
|
+ *
|
|
|
+ * If property does not exist, you shouldn't throw exception, but
|
|
|
+ * you should change "status.error" flag and return null.
|
|
|
+ *
|
|
|
+ * @param status (output) status
|
|
|
+ * @param that that
|
|
|
+ * @param name name
|
|
|
+ * @param value value
|
|
|
+ * @return Object
|
|
|
+ */
|
|
|
public abstract Object replace(ResolveStatus status, Object that, String name, Object value);
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns deduced type of property from "that" type.
|
|
|
+ *
|
|
|
+ * If property does not exist, you shouldn't throw exception, but
|
|
|
+ * you should change "status.error" flag and return null.
|
|
|
+ *
|
|
|
+ * Please remember that type deduction works without instance objects.
|
|
|
+ * For example, for array type, you just return "component" type.
|
|
|
+ *
|
|
|
+ * @param status status
|
|
|
+ * @param that that
|
|
|
+ * @param name name
|
|
|
+ * @return IClass
|
|
|
+ */
|
|
|
public abstract IClass<?> type(ResolveStatus status, IClass<?> that, String name);
|
|
|
|
|
|
+ /**
|
|
|
+ * Model for array types
|
|
|
+ *
|
|
|
+ * It will match if the object is array and field name is
|
|
|
+ * - numerical value
|
|
|
+ * - "length"
|
|
|
+ *
|
|
|
+ */
|
|
|
public static final ResolveModel ARRAY = new ResolveModel() {
|
|
|
@Override
|
|
|
public boolean matches(Object that, String name) {
|
|
|
@@ -74,6 +165,15 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * Model for List types.
|
|
|
+ *
|
|
|
+ * It will match if the object is List and field name is numerical value.
|
|
|
+ *
|
|
|
+ * Please note that it won't match if you try to call some method declared
|
|
|
+ * by list (for example "size"). Such cases are handled by other models.
|
|
|
+ *
|
|
|
+ */
|
|
|
public static final ResolveModel LIST = new ResolveModel() {
|
|
|
@Override
|
|
|
public boolean matches(Object that, String name) {
|
|
|
@@ -112,6 +212,14 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * Model for Map types.
|
|
|
+ *
|
|
|
+ * It will match if the object implements Map
|
|
|
+ *
|
|
|
+ * Please note that it is impossible to call any method declared by Map.
|
|
|
+ * Every field will be access using "get" and "put" operations.
|
|
|
+ */
|
|
|
public static final ResolveModel MAP = new ResolveModel() {
|
|
|
@Override
|
|
|
public boolean matches(Object that, String _name) {
|
|
|
@@ -150,6 +258,15 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * Model for Matcher types.
|
|
|
+ *
|
|
|
+ * It will match if the object is {@link Matcher}
|
|
|
+ *
|
|
|
+ * Please note that it is impossible to call any method declared by Matcher.
|
|
|
+ * Every field will be accessed using "group" method.
|
|
|
+ * You can access matched groups by index or by name.
|
|
|
+ */
|
|
|
public static final ResolveModel MATCHER = new ResolveModel() {
|
|
|
@Override
|
|
|
public boolean matches(Object that, String _name) {
|
|
|
@@ -163,6 +280,16 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
|
|
|
@Override
|
|
|
public Object get(ResolveStatus _s, Object that, String name) {
|
|
|
+ // @todo fix resolver: we can't read named group, if the name is "numerical"
|
|
|
+ // we should try to get group by name even if it is a number
|
|
|
+ // catch exception and then try to get group by index
|
|
|
+ //
|
|
|
+ // more correct, but much much slower
|
|
|
+ // altough resolve is not optimized for performance anyway
|
|
|
+ //
|
|
|
+ // maybe we should try to use just different syntax for
|
|
|
+ // index and name, for example "$index", ":name"
|
|
|
+
|
|
|
if (StringTraits.isNumber(name)) {
|
|
|
return ((Matcher) that).group(Integer.parseInt(name));
|
|
|
} else {
|
|
|
@@ -186,6 +313,16 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * Model for CharSequence types
|
|
|
+ *
|
|
|
+ * It will match if the object is CharSequence and field name is numerical value.
|
|
|
+ *
|
|
|
+ * Please note that it won't match if you try to call some method or getter
|
|
|
+ * (for example "length"). Such cases are handled by other models.
|
|
|
+ *
|
|
|
+ * @todo fix resolver: fix behaviour to match descritpion, do it like for LIST
|
|
|
+ */
|
|
|
public static final ResolveModel CHARS = new ResolveModel() {
|
|
|
@Override
|
|
|
public boolean matches(Object that, String _name) {
|
|
|
@@ -218,6 +355,20 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * Model which works for any object
|
|
|
+ *
|
|
|
+ * It will match for every non-null value, so it should be inserted into
|
|
|
+ * ResolveConfiguration at the very end.
|
|
|
+ *
|
|
|
+ * It uses {@link BeanModel#fluent(Object)} to call getter method.
|
|
|
+ * If there is no getter, then it tries to read field value using reflection.
|
|
|
+ *
|
|
|
+ * Please note, it read even private fields (it shouldn't)
|
|
|
+ *
|
|
|
+ * @todo fix resolver: read only public fields
|
|
|
+ * @todo fix resolver: optimize this shit
|
|
|
+ */
|
|
|
public static final ResolveModel BEAN = new ResolveModel() {
|
|
|
@Override
|
|
|
public boolean matches(Object that, String _name) {
|
|
|
@@ -292,22 +443,33 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * Model for ResolveContext
|
|
|
+ *
|
|
|
+ * It will match if the object implements ResolveContext
|
|
|
+ *
|
|
|
+ * Please note that it is impossible to call any method declared by ResolveContext.
|
|
|
+ * Every field will be accessed through context map.
|
|
|
+ *
|
|
|
+ * ResolveContext properties are handled in specific way
|
|
|
+ * if their type is ResolveFormat or ResolvePattern.
|
|
|
+ * Those objects will be read and then evaluated against ResolveContext.
|
|
|
+ */
|
|
|
public static final ResolveModel CONTEXT = new ResolveModel() {
|
|
|
@Override
|
|
|
public boolean matches(Object that, String name) {
|
|
|
- return (that instanceof ResolveContext) && ((ResolveContext) that).getResolveMap().containsKey(name);
|
|
|
+ return (that instanceof ResolveContext);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean matchesType(IClass<?> that, String _name) {
|
|
|
- return that!=IClass.NULL && IClass.typeinfo(ResolveContext.class).isSuper(that);
|
|
|
+ return that.isSubclass(ResolveContext.class);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Object get(ResolveStatus status, Object that, String name) {
|
|
|
- Optional<Object> out = ((ResolveContext) that).getResolveMap().getOptional(name);
|
|
|
- if(out.isPresent()) {
|
|
|
- Object result = out.get();
|
|
|
+ Object result = ((ResolveContext) that).getResolveMap().get(name);
|
|
|
+ if(result != null) {
|
|
|
if(result instanceof ResolvePattern) {
|
|
|
ResolvePattern pattern = (ResolvePattern) result;
|
|
|
return pattern.get(that);
|
|
|
@@ -335,33 +497,51 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
|
|
|
@Override
|
|
|
public IClass<?> type(ResolveStatus status, IClass<?> that, String name) {
|
|
|
- Object result = get(status, that, name);
|
|
|
- if(result instanceof ResolvePattern) {
|
|
|
- ResolvePattern pattern = (ResolvePattern) result;
|
|
|
- return pattern.type(that);
|
|
|
- }
|
|
|
- if(result instanceof ResolveFormat) {
|
|
|
- return IClass.STRING;
|
|
|
- }
|
|
|
- return status.error ? null : IClass.typefor(result);
|
|
|
+ return IClass.OBJECT;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ /**
|
|
|
+ * This class is simple wrapper which is used as "output parameter".
|
|
|
+ *
|
|
|
+ * Methods inside ResolveModel can set error flag to inform that property does not exist.
|
|
|
+ */
|
|
|
protected static final class ResolveStatus {
|
|
|
|
|
|
public boolean error;
|
|
|
|
|
|
}
|
|
|
|
|
|
- public static abstract class AbstractGetter<T> extends ResolveModel {
|
|
|
+ /**
|
|
|
+ * This base abstract class can be used to write trivial ResolveModel which
|
|
|
+ * supports only read-only view and does not support type deduction.
|
|
|
+ *
|
|
|
+ * You have to implement only "get" method which returns property value as Optional.
|
|
|
+ *
|
|
|
+ * Please note that this model will consume objects of type T in every case, even if it can't find field.
|
|
|
+ *
|
|
|
+ * Please note that this model is unable to make type deducation and it will always return OBJECT.
|
|
|
+ *
|
|
|
+ * @param <T> T
|
|
|
+ */
|
|
|
+ public static abstract class AbstractView<T> extends ResolveModel {
|
|
|
|
|
|
private final IClass<T> type;
|
|
|
|
|
|
- public AbstractGetter(Class<T> type) {
|
|
|
+ protected AbstractView(Class<T> type) {
|
|
|
this.type = IClass.typeinfo(type);
|
|
|
}
|
|
|
|
|
|
- public abstract Optional<Object> get(T that, String name);
|
|
|
+ /**
|
|
|
+ * Method should be implemented by subclass.
|
|
|
+ *
|
|
|
+ * If property with provided name does not exist, should return Optional.empty()
|
|
|
+ *
|
|
|
+ * @param that that
|
|
|
+ * @param name name
|
|
|
+ * @return Optional
|
|
|
+ */
|
|
|
+ protected abstract Optional<Object> get(T that, String name);
|
|
|
|
|
|
@Override
|
|
|
public boolean matchesType(IClass<?> that, String name) {
|
|
|
@@ -370,20 +550,24 @@ public abstract class ResolveModel extends SerializableCode {
|
|
|
|
|
|
@Override
|
|
|
public boolean matches(Object that, String name) {
|
|
|
- return get(null, that, name) != null;
|
|
|
+ return type.isInstance(that);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public Object get(ResolveStatus _s, Object that, String name) {
|
|
|
+ public Object get(ResolveStatus status, Object that, String name) {
|
|
|
if(type.isInstance(that)) {
|
|
|
- return get(type.cast(that), name).orElse(null);
|
|
|
+ Optional<Object> out = get(type.cast(that), name);
|
|
|
+ if(out.isPresent()) {
|
|
|
+ return out.get();
|
|
|
+ }
|
|
|
}
|
|
|
+ status.error = true;
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public IClass<?> type(ResolveStatus status, IClass<?> that, String name) {
|
|
|
- return IClass.typefor(get(status, that, name));
|
|
|
+ return IClass.OBJECT;
|
|
|
}
|
|
|
|
|
|
@Override
|