[Major] Refactored searching to allow for coercing of left side

This commit is contained in:
Robert von Burg 2018-04-20 20:49:31 +02:00
parent 7deafa533d
commit 1e616f0616
29 changed files with 402 additions and 70 deletions

View File

@ -7,6 +7,10 @@ public interface ExpressionBuilder<T extends StrolchRootElement> {
Object extract(StrolchRootElement element);
default ValueCoercer getValueCoercer(StrolchRootElement context) {
return e -> e;
}
default SearchExpression<T> isEqualTo(Object right) {
return element -> PredicatesSupport.isEqualTo(right).matches(extract(element));
}

View File

@ -3,6 +3,7 @@ package li.strolch.search;
import li.strolch.model.*;
import li.strolch.model.activity.Activity;
import li.strolch.model.parameter.Parameter;
import li.strolch.utils.iso8601.ISO8601FormatFactory;
public class ExpressionsSupport {
@ -10,58 +11,111 @@ public class ExpressionsSupport {
return element -> !expression.matches(element);
}
public static <T extends StrolchRootElement> ExpressionBuilder<T> id() {
return StrolchElement::getId;
}
public static <T extends StrolchRootElement> SearchExpression<T> id(SearchPredicate predicate) {
return element -> predicate.matches(element.getId());
}
public static <T extends StrolchRootElement> ExpressionBuilder<T> name() {
return StrolchElement::getName;
public static <T extends StrolchRootElement> ExpressionBuilder<T> id() {
return StrolchElement::getId;
}
public static <T extends StrolchRootElement> SearchExpression<T> name(SearchPredicate predicate) {
return element -> predicate.matches(element.getName());
}
public static <T extends StrolchRootElement> ExpressionBuilder<T> date() {
return element -> ((Order) element).getDate();
public static <T extends StrolchRootElement> ExpressionBuilder<T> name() {
return StrolchElement::getName;
}
public static <T extends StrolchRootElement> SearchExpression<T> date(SearchPredicate predicate) {
return element -> predicate.matches(((Order) element).getDate());
ExpressionBuilder<StrolchRootElement> eb = date();
return element -> predicate.coerce(eb.getValueCoercer(element)).matches(eb);
}
public static <T extends StrolchRootElement> ExpressionBuilder<T> state() {
return element -> {
if (element instanceof Order)
return ((Order) element).getState();
if (element instanceof Activity)
return ((Activity) element).getState();
throw new IllegalArgumentException(element.getObjectType() + " does not have a state!");
public static <T extends StrolchRootElement> ExpressionBuilder<T> date() {
return new ExpressionBuilder<T>() {
@Override
public ValueCoercer getValueCoercer(StrolchRootElement context) {
return e -> {
if (!(e instanceof String))
return e;
return ISO8601FormatFactory.getInstance().parseDate((String) e);
};
}
@Override
public Object extract(StrolchRootElement element) {
return ((Order) element).getDate();
}
};
}
public static <T extends StrolchRootElement> SearchExpression<T> state(SearchPredicate predicate) {
return element -> predicate.matches(state().extract(element));
ExpressionBuilder<StrolchRootElement> eb = state();
return element -> predicate.coerce(eb.getValueCoercer(element)).matches(eb.extract(element));
}
public static <T extends StrolchRootElement> ExpressionBuilder<T> param(String bagId, String paramId) {
return element -> {
ParameterBag bag = element.getParameterBag(bagId);
if (bag == null)
return null;
public static <T extends StrolchRootElement> ExpressionBuilder<T> state() {
return new ExpressionBuilder<T>() {
Parameter<?> param = bag.getParameter(paramId);
return param == null ? null : param.getValue();
@Override
public ValueCoercer getValueCoercer(StrolchRootElement context) {
return e -> {
if (!(e instanceof String))
return e;
return State.parse((String) e);
};
}
@Override
public Object extract(StrolchRootElement element) {
if (element instanceof Order)
return ((Order) element).getState();
if (element instanceof Activity)
return ((Activity) element).getState();
throw new IllegalArgumentException(element.getObjectType() + " does not have a state!");
}
};
}
public static <T extends StrolchRootElement> SearchExpression<T> param(String bagId, String paramId,
SearchPredicate predicate) {
return element -> predicate.matches(param(bagId, paramId).extract(element));
ExpressionBuilder<StrolchRootElement> eb = param(bagId, paramId);
return element -> predicate.coerce(eb.getValueCoercer(element)).matches(eb.extract(element));
}
public static <T extends StrolchRootElement> ExpressionBuilder<T> param(String bagId, String paramId) {
return new ExpressionBuilder<T>() {
@Override
public ValueCoercer getValueCoercer(StrolchRootElement context) {
return e -> {
if (!(e instanceof String))
return e;
ParameterBag bag = context.getParameterBag(bagId);
if (bag == null)
return e;
Parameter<?> param = bag.getParameter(paramId);
if (param == null)
return e;
return param.getValueType().parseValue((String) e);
};
}
@Override
public Object extract(StrolchRootElement element) {
ParameterBag bag = element.getParameterBag(bagId);
if (bag == null)
return null;
Parameter<?> param = bag.getParameter(paramId);
return param == null ? null : param.getValue();
}
};
}
public static <T extends StrolchRootElement> SearchExpression<T> paramNull(String bagId, String paramId) {

View File

@ -1,9 +0,0 @@
package li.strolch.search;
public class ParameterPredicate implements SearchPredicate {
@Override
public boolean matches(Object value) {
return false;
}
}

View File

@ -1,61 +1,59 @@
package li.strolch.search;
import java.util.Date;
import li.strolch.utils.ObjectHelper;
import li.strolch.search.predicates.*;
import li.strolch.utils.collections.DateRange;
public class PredicatesSupport {
public static SearchPredicate isEqualTo(Object right) {
return left -> ObjectHelper.equals(left, right, false);
return new IsEqualToPredicate(right, false);
}
public static SearchPredicate isEqualToIgnoreCase(Object right) {
return left -> ObjectHelper.equals(left, right, true);
return new IsEqualToPredicate(right, true);
}
public static SearchPredicate isNotEqualTo(Object right) {
return left -> !ObjectHelper.equals(left, right, false);
return new IsEqualToPredicate(right, false).not();
}
public static SearchPredicate isNotEqualToIgnoreCase(Object right) {
return left -> !ObjectHelper.equals(left, right, true);
return new IsEqualToPredicate(right, true).not();
}
public static SearchPredicate startsWith(Object right) {
return left -> ObjectHelper.startsWith(left, right, false);
return new StartsWithPredicate(right, false);
}
public static SearchPredicate startsWithIgnoreCase(Object right) {
return left -> ObjectHelper.startsWith(left, right, true);
return new StartsWithPredicate(right, true);
}
public static SearchPredicate endsWith(Object right) {
return left -> ObjectHelper.endsWith(left, right, false);
return new EndsWithPredicate(right, false);
}
public static SearchPredicate endsWithIgnoreCase(Object right) {
return left -> ObjectHelper.endsWith(left, right, true);
return new EndsWithPredicate(right, true);
}
public static SearchPredicate contains(Object right) {
return left -> ObjectHelper.contains(left, right, false);
return new ContainsPredicate(right, false);
}
public static SearchPredicate containsIgnoreCase(Object right) {
return left -> ObjectHelper.contains(left, right, true);
return new ContainsPredicate(right, true);
}
public static SearchPredicate isIn(Object right) {
return left -> ObjectHelper.isIn(left, right, false);
return new IsInPredicate(right, false);
}
public static SearchPredicate isInIgnoreCase(Object right) {
return left -> ObjectHelper.isIn(left, right, true);
return new IsInPredicate(right, true);
}
public static SearchPredicate inRange(DateRange range) {
return left -> range.contains((Date) left);
return new InRangePredicate(range);
}
}

View File

@ -1,10 +1,14 @@
package li.strolch.search;
import li.strolch.search.predicates.NotPredicate;
public interface SearchPredicate {
boolean matches(Object value);
boolean matches(Object left);
SearchPredicate coerce(ValueCoercer coercer);
default SearchPredicate not() {
return element -> !this.matches(element);
return new NotPredicate(this);
}
}

View File

@ -0,0 +1,6 @@
package li.strolch.search;
public interface ValueCoercer {
Object coerce(Object value);
}

View File

@ -0,0 +1,23 @@
package li.strolch.search.predicates;
import li.strolch.search.SearchPredicate;
import li.strolch.search.ValueCoercer;
public abstract class AbstractSearchPredicate implements SearchPredicate {
private boolean coerced;
protected Object right;
public AbstractSearchPredicate(Object right) {
this.right = right;
}
public AbstractSearchPredicate coerce(ValueCoercer coercer) {
if (this.coerced)
return this;
this.right = coercer.coerce(this.right);
this.coerced = true;
return this;
}
}

View File

@ -0,0 +1,17 @@
package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
public class ContainsPredicate extends AbstractSearchPredicate {
private boolean ignoreCase;
public ContainsPredicate(Object right, boolean ignoreCase) {
super(right);
this.ignoreCase = ignoreCase;
}
@Override
public boolean matches(Object left) {
return ObjectHelper.contains(left, this.right, this.ignoreCase);
}
}

View File

@ -0,0 +1,17 @@
package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
public class EndsWithPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;
public EndsWithPredicate(Object right, boolean ignoreCase) {
super(right);
this.ignoreCase = ignoreCase;
}
@Override
public boolean matches(Object left) {
return ObjectHelper.endsWith(left, this.right, this.ignoreCase);
}
}

View File

@ -0,0 +1,26 @@
package li.strolch.search.predicates;
import java.util.Date;
import li.strolch.search.SearchPredicate;
import li.strolch.search.ValueCoercer;
import li.strolch.utils.collections.DateRange;
public class InRangePredicate implements SearchPredicate {
private final DateRange range;
public InRangePredicate(DateRange range) {
this.range = range;
}
@Override
public boolean matches(Object left) {
return range.contains((Date) left);
}
@Override
public SearchPredicate coerce(ValueCoercer coercer) {
// nothing to coerce
return this;
}
}

View File

@ -0,0 +1,17 @@
package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
public class IsEqualToPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;
public IsEqualToPredicate(Object right, boolean ignoreCase) {
super(right);
this.ignoreCase = ignoreCase;
}
@Override
public boolean matches(Object left) {
return ObjectHelper.equals(left, this.right, this.ignoreCase);
}
}

View File

@ -0,0 +1,17 @@
package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
public class IsInPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;
public IsInPredicate(Object right, boolean ignoreCase) {
super(right);
this.ignoreCase = ignoreCase;
}
@Override
public boolean matches(Object left) {
return ObjectHelper.isIn(left, this.right, this.ignoreCase);
}
}

View File

@ -0,0 +1,24 @@
package li.strolch.search.predicates;
import li.strolch.search.SearchPredicate;
import li.strolch.search.ValueCoercer;
public class NotPredicate implements SearchPredicate {
private final SearchPredicate predicate;
public NotPredicate(SearchPredicate predicate) {
this.predicate = predicate;
}
@Override
public SearchPredicate coerce(ValueCoercer coercer) {
this.predicate.coerce(coercer);
return this;
}
@Override
public boolean matches(Object left) {
return !this.predicate.matches(left);
}
}

View File

@ -0,0 +1,17 @@
package li.strolch.search.predicates;
import li.strolch.utils.ObjectHelper;
public class StartsWithPredicate extends AbstractSearchPredicate {
private final boolean ignoreCase;
public StartsWithPredicate(Object right, boolean ignoreCase) {
super(right);
this.ignoreCase = ignoreCase;
}
@Override
public boolean matches(Object left) {
return ObjectHelper.startsWith(left, this.right, this.ignoreCase);
}
}

View File

@ -378,16 +378,21 @@ public class StrolchSearchTest {
.and(param(BAG_ID, PARAM_STRING_ID, endsWithIgnoreCase("LCH")))
.and(param(BAG_ID, PARAM_BOOLEAN_ID, isEqualTo(true)))
.and(param(BAG_ID, PARAM_BOOLEAN_ID, isEqualTo("true")))
.and(param(BAG_ID, PARAM_DATE_ID, isEqualTo(new Date(1354295525628L))))
.and(param(BAG_ID, PARAM_DATE_ID,
isEqualTo(ISO8601FormatFactory.getInstance().formatDate(new Date(1354295525628L)))))
.and(param(BAG_ID, PARAM_INTEGER_ID, isEqualTo(77))) //
.and(param(BAG_ID, PARAM_INTEGER_ID, isIn(77))) //
.and(param(BAG_ID, PARAM_INTEGER_ID, isIn("77"))) //
.and(param(BAG_ID, PARAM_INTEGER_ID, isIn(77, 88))) //
.and(param(BAG_ID, PARAM_INTEGER_ID, isIn(asList(77, 88)))) //
.and(param(BAG_ID, PARAM_LIST_FLOAT_ID, isEqualTo(asList(6.0D, 11.0D, 16.0D))))
.and(param(BAG_ID, PARAM_LIST_FLOAT_ID, contains(singletonList(6.0D))))
.and(param(BAG_ID, PARAM_LIST_FLOAT_ID, contains(asList(6.0D, 11.0D))))
.and(param(BAG_ID, PARAM_LIST_FLOAT_ID, contains("6.0D,11.0D")))
.and(param(BAG_ID, PARAM_LIST_INTEGER_ID, isEqualTo(asList(5, 10, 15))))
.and(param(BAG_ID, PARAM_LIST_INTEGER_ID, contains(asList(5, 10))))

View File

@ -17,24 +17,8 @@ package li.strolch.model;
import java.text.MessageFormat;
import li.strolch.model.parameter.BooleanParameter;
import li.strolch.model.parameter.DateParameter;
import li.strolch.model.parameter.DurationParameter;
import li.strolch.model.parameter.FloatListParameter;
import li.strolch.model.parameter.FloatParameter;
import li.strolch.model.parameter.IntegerListParameter;
import li.strolch.model.parameter.IntegerParameter;
import li.strolch.model.parameter.LongListParameter;
import li.strolch.model.parameter.LongParameter;
import li.strolch.model.parameter.Parameter;
import li.strolch.model.parameter.StringListParameter;
import li.strolch.model.parameter.StringParameter;
import li.strolch.model.timedstate.BooleanTimedState;
import li.strolch.model.timedstate.FloatTimedState;
import li.strolch.model.timedstate.IntegerTimedState;
import li.strolch.model.timedstate.StringSetTimedState;
import li.strolch.model.timedstate.StrolchTimedState;
import li.strolch.model.timedstate.TimedState;
import li.strolch.model.parameter.*;
import li.strolch.model.timedstate.*;
import li.strolch.model.timevalue.IValue;
import li.strolch.model.timevalue.IValueChange;
import li.strolch.model.timevalue.impl.BooleanValue;
@ -54,6 +38,11 @@ public enum StrolchValueType {
* </ul>
*/
BOOLEAN("Boolean") {
@Override
public Object parseValue(String value) {
return BooleanParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new BooleanParameter();
@ -85,6 +74,11 @@ public enum StrolchValueType {
* </ul>
*/
INTEGER("Integer") {
@Override
public Object parseValue(String value) {
return IntegerParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new IntegerParameter();
@ -116,6 +110,11 @@ public enum StrolchValueType {
* </ul>
*/
FLOAT("Float") {
@Override
public Object parseValue(String value) {
return FloatParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new FloatParameter();
@ -144,6 +143,11 @@ public enum StrolchValueType {
* </ul>
*/
LONG("Long") {
@Override
public Object parseValue(String value) {
return LongParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new LongParameter();
@ -174,6 +178,11 @@ public enum StrolchValueType {
* </ul>
*/
STRING("String") {
@Override
public Object parseValue(String value) {
return value;
}
@Override
public Parameter<?> parameterInstance() {
return new StringParameter();
@ -204,6 +213,11 @@ public enum StrolchValueType {
* </ul>
*/
DATE("Date") {
@Override
public Object parseValue(String value) {
return DateParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new DateParameter();
@ -229,6 +243,11 @@ public enum StrolchValueType {
* </ul>
*/
DURATION("Duration") {
@Override
public Object parseValue(String value) {
return DurationParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new DurationParameter();
@ -254,6 +273,11 @@ public enum StrolchValueType {
* </ul>
*/
FLOAT_LIST("FloatList") {
@Override
public Object parseValue(String value) {
return FloatListParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new FloatListParameter();
@ -279,6 +303,11 @@ public enum StrolchValueType {
* </ul>
*/
INTEGER_LIST("IntegerList") {
@Override
public Object parseValue(String value) {
return IntegerListParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new IntegerListParameter();
@ -304,6 +333,11 @@ public enum StrolchValueType {
* </ul>
*/
LONG_LIST("LongList") {
@Override
public Object parseValue(String value) {
return LongListParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new LongListParameter();
@ -329,6 +363,11 @@ public enum StrolchValueType {
* </ul>
*/
STRING_LIST("StringList") {
@Override
public Object parseValue(String value) {
return StringListParameter.parseFromString(value);
}
@Override
public Parameter<?> parameterInstance() {
return new StringListParameter();
@ -356,6 +395,12 @@ public enum StrolchValueType {
* </ul>
*/
STRING_SET("StringSet") {
@Override
public Object parseValue(String value) {
throw new UnsupportedOperationException(
MessageFormat.format("Parsing value of type {0} is not supported!", getType())); //$NON-NLS-1$
}
@Override
public Parameter<?> parameterInstance() {
throw new UnsupportedOperationException(
@ -395,6 +440,8 @@ public enum StrolchValueType {
throw new IllegalArgumentException("Type " + value + " does not exist!");
}
public abstract Object parseValue(String value);
public abstract Parameter<?> parameterInstance();
public abstract StrolchTimedState<? extends IValue<?>> timedStateInstance();

View File

@ -117,4 +117,9 @@ public class BooleanParameter extends AbstractParameter<Boolean> {
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return this.getValue().compareTo(((BooleanParameter) o).getValue());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.BOOLEAN;
}
}

View File

@ -123,4 +123,9 @@ public class DateParameter extends AbstractParameter<Date> {
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return this.getValue().compareTo(((DateParameter) o).getValue());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.DATE;
}
}

View File

@ -120,4 +120,9 @@ public class DurationParameter extends AbstractParameter<Long> {
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return this.getValue().compareTo(((DurationParameter) o).getValue());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.DURATION;
}
}

View File

@ -177,4 +177,9 @@ public class FloatListParameter extends AbstractParameter<List<Double>> implemen
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return Integer.compare(this.getValue().size(), ((FloatListParameter) o).getValue().size());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.FLOAT_LIST;
}
}

View File

@ -121,4 +121,9 @@ public class FloatParameter extends AbstractParameter<Double> {
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return this.getValue().compareTo(((FloatParameter) o).getValue());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.FLOAT;
}
}

View File

@ -177,4 +177,9 @@ public class IntegerListParameter extends AbstractParameter<List<Integer>> imple
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return Integer.compare(this.getValue().size(), ((IntegerListParameter) o).getValue().size());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.INTEGER_LIST;
}
}

View File

@ -119,4 +119,9 @@ public class IntegerParameter extends AbstractParameter<Integer> {
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return this.getValue().compareTo(((IntegerParameter) o).getValue());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.INTEGER;
}
}

View File

@ -177,4 +177,9 @@ public class LongListParameter extends AbstractParameter<List<Long>> implements
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return Integer.compare(this.getValue().size(), ((LongListParameter) o).getValue().size());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.LONG_LIST;
}
}

View File

@ -119,4 +119,9 @@ public class LongParameter extends AbstractParameter<Long> {
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return this.getValue().compareTo(((LongParameter) o).getValue());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.LONG;
}
}

View File

@ -18,6 +18,7 @@ package li.strolch.model.parameter;
import li.strolch.model.ParameterizedElement;
import li.strolch.model.StrolchElement;
import li.strolch.model.StrolchModelConstants;
import li.strolch.model.StrolchValueType;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
@ -161,4 +162,9 @@ public interface Parameter<T> extends StrolchElement, Comparable<Parameter<?>> {
@Override
public Parameter<T> getClone();
/**
* @return the {@link StrolchValueType}
*/
public StrolchValueType getValueType();
}

View File

@ -177,4 +177,9 @@ public class StringListParameter extends AbstractParameter<List<String>> impleme
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return Integer.compare(this.getValue().size(), ((StringListParameter) o).getValue().size());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.STRING_LIST;
}
}

View File

@ -117,4 +117,9 @@ public class StringParameter extends AbstractParameter<String> {
DBC.PRE.assertEquals("Not same Parameter types!", this.getType(), o.getType());
return this.getValue().compareToIgnoreCase(((StringParameter) o).getValue());
}
@Override
public StrolchValueType getValueType() {
return StrolchValueType.STRING;
}
}

View File

@ -94,6 +94,10 @@ public class ObjectHelper {
}
}
// comparing non-strings we use equals, as contains fits as well
if (left.getClass() == right.getClass())
return left.equals(right);
throw new IllegalArgumentException("Unhandled type combination " + left.getClass() + " / " + right.getClass());
}