Skip to content

Commit

Permalink
Codegen Support for nested (object) Criterias
Browse files Browse the repository at this point in the history
Allow user to chain methods like `person.friend.name.isEqualTo("John")`
  • Loading branch information
asereda-gs committed Apr 8, 2019
1 parent 5bf91b5 commit bc21046
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 139 deletions.
Expand Up @@ -16,20 +16,17 @@

package org.immutables.criteria;

import org.immutables.criteria.constraints.Expressional;

/**
* Base class of Criteria API. Right now used as a marker interface.
* Generated code extends this class.
*
* @param <C> Criteria self-type, allowing {@code this}-returning methods to avoid needing subclassing
* @param <T> type of the document being evaluated by this criteria
* @param <R> Criteria self-type, allowing {@code this}-returning methods to avoid needing subclassing
*/
public interface DocumentCriteria<C extends DocumentCriteria<C, T>, T> extends Expressional<T> {
public interface DocumentCriteria<R extends DocumentCriteria<R>> {

/**
* Builds a disjunction
*/
C or();
R or();

}
27 changes: 0 additions & 27 deletions criteria/common/src/org/immutables/criteria/ValueCriteria.java

This file was deleted.

Expand Up @@ -21,18 +21,18 @@
/**
* Very simple criteria for booleans just has {@code true} / {@code false} checks.
*/
public class BooleanCriteria<C extends DocumentCriteria<C, T>, T> extends ObjectCriteria<Boolean, C, T> {
public class BooleanCriteria<R extends DocumentCriteria<R>> extends ObjectCriteria<R, Boolean> {

public BooleanCriteria(CriteriaCreator<C, T> creator) {
super(creator);
public BooleanCriteria(CriteriaContext<R> context) {
super(context);
}

public C isTrue() {
return create(e -> Expressions.<T>call(Operators.EQUAL, e, Expressions.literal(Boolean.TRUE)));
public R isTrue() {
return create(e -> Expressions.call(Operators.EQUAL, e, Expressions.literal(Boolean.TRUE)));
}

public C isFalse() {
return create(e -> Expressions.<T>call(Operators.EQUAL, e, Expressions.literal(Boolean.FALSE)));
public R isFalse() {
return create(e -> Expressions.call(Operators.EQUAL, e, Expressions.literal(Boolean.FALSE)));
}

}
Expand Up @@ -21,41 +21,41 @@
/**
* Criteria for comparables (like {@code >, <=, >} and ranges).
*/
public class ComparableCriteria<V extends Comparable<V>, C extends DocumentCriteria<C, T>, T>
extends ObjectCriteria<V, C, T> {
public class ComparableCriteria<R extends DocumentCriteria<R>, V extends Comparable<V>>
extends ObjectCriteria<R, V> {

public ComparableCriteria(CriteriaCreator<C, T> creator) {
super(creator);
public ComparableCriteria(CriteriaContext<R> context) {
super(context);
}

/**]
* Checks that attribute is less than (but not equal to) {@code upper}.
* <p>Use {@link #isAtMost(Comparable)} for less <i>or equal</i> comparison</p>
*/
public C isLessThan(V upper) {
return create(e -> Expressions.<T>call(Operators.LESS_THAN, e, Expressions.literal(upper)));
public R isLessThan(V upper) {
return create(e -> Expressions.call(Operators.LESS_THAN, e, Expressions.literal(upper)));
}

/**
* Checks that attribute is greater than (but not equal to) {@code lower}.
* <p>Use {@link #isAtLeast(Comparable)} for greater <i>or equal</i> comparison</p>
*/
public C isGreaterThan(V lower) {
return create(e -> Expressions.<T>call(Operators.GREATER_THAN, e, Expressions.literal(lower)));
public R isGreaterThan(V lower) {
return create(e -> Expressions.call(Operators.GREATER_THAN, e, Expressions.literal(lower)));
}

/**
* Checks that attribute is less than or equal to {@code upperInclusive}.
*/
public C isAtMost(V upperInclusive) {
return create(e -> Expressions.<T>call(Operators.LESS_THAN_OR_EQUAL, e, Expressions.literal(upperInclusive)));
public R isAtMost(V upperInclusive) {
return create(e -> Expressions.call(Operators.LESS_THAN_OR_EQUAL, e, Expressions.literal(upperInclusive)));
}

/**
* Checks that attribute is greater or equal to {@code lowerInclusive}.
*/
public C isAtLeast(V lowerInclusive) {
return create(e -> Expressions.<T>call(Operators.GREATER_THAN_OR_EQUAL, e, Expressions.literal(lowerInclusive)));
public R isAtLeast(V lowerInclusive) {
return create(e -> Expressions.call(Operators.GREATER_THAN_OR_EQUAL, e, Expressions.literal(lowerInclusive)));
}

}
@@ -0,0 +1,60 @@
package org.immutables.criteria.constraints;

import org.immutables.criteria.DocumentCriteria;

import java.util.function.UnaryOperator;

/**
* Link between front-end (codegened Criteria) and back-end (built {@link Expression}).
*/
public final class CriteriaContext<R extends DocumentCriteria<R>> {

private final CriteriaCreator<R> creator;
private final DnfExpression<?> expression;
private final Path<?> path;
private final Operator operator;

public CriteriaContext(CriteriaCreator<R> creator) {
this(Operators.AND, DnfExpression.create(), null, creator);
}

private CriteriaContext(Operator operator, DnfExpression<?> expression, Path<?> path, CriteriaCreator<R> creator) {
this.creator = creator;
this.expression = expression;
this.path = path;
this.operator = operator;
}

public R create() {
return creator.create(this);
}

/**
* adds an intermediate step (list of paths usually)
*/
public CriteriaContext<R> add(Path<?> path) {
final Path<?> newPath = this.path != null ? Expressions.path(this.path.path() + "." + path.path()) : path;
return new CriteriaContext<>(operator, expression, newPath, creator);
}

public CriteriaContext<R> or() {
if (operator == Operators.OR) {
return this;
}

return new CriteriaContext<>(Operators.OR, expression, path, creator);
}

public Expression<?> expression() {
return this.expression;
}

@SuppressWarnings("unchecked")
public R create(UnaryOperator<Expression<?>> operator) {
final Expression<Object> apply = (Expression<Object>) operator.apply(path);
final DnfExpression<Object> existing = (DnfExpression<Object>) expression;
final DnfExpression<?> newExpression = this.operator == Operators.AND ? existing.and(apply) : existing.or(apply);
return new CriteriaContext<R>(Operators.AND, newExpression, null, creator).create();
}

}
Expand Up @@ -17,13 +17,11 @@

import org.immutables.criteria.DocumentCriteria;

import java.util.function.UnaryOperator;

/**
* Creates document criteria from existing expression.
*/
public interface CriteriaCreator<C extends DocumentCriteria<C, T>, T> {
public interface CriteriaCreator<R extends DocumentCriteria<R>> {

C create(UnaryOperator<Expression<T>> expr);
R create(CriteriaContext<R> context);

}
Expand Up @@ -24,9 +24,8 @@ private DnfExpression(List<Expression<T>> conjunctions, List<Expression<T>> disj
this.disjunctions = ImmutableList.copyOf(disjunctions);
}

static <T> DnfExpression<T> create(Expression<T> existing) {
Objects.requireNonNull(existing, "existing");
return new DnfExpression<>(ImmutableList.of(existing), Collections.emptyList());
static <T> DnfExpression<T> create() {
return new DnfExpression<>(Collections.emptyList(), Collections.emptyList());
}

@Nullable
Expand All @@ -45,13 +44,13 @@ private Expression<T> simplify() {
}


Expression<T> and(Expression<T> expression) {
DnfExpression<T> and(Expression<T> expression) {
Objects.requireNonNull(expression, "expression");
ImmutableList<Expression<T>> newConjunctions = ImmutableList.<Expression<T>>builder().addAll(conjunctions).add(expression).build();
return new DnfExpression<T>(newConjunctions, disjunctions);
}

Expression<T> or(Expression<T> expression) {
DnfExpression<T> or(Expression<T> expression) {
Objects.requireNonNull(expression, "expression");
List<Expression<T>> newDisjunction = ImmutableList.<Expression<T>>builder().addAll(disjunctions).add(Expressions.and(conjunctions)).build();
return new DnfExpression<>(ImmutableList.of(expression), newDisjunction);
Expand Down
Expand Up @@ -86,7 +86,7 @@ public static <T> Expression<T> dnf(Operator operator, Expression<T> existing, E
}

if (isNil(existing)) {
return DnfExpression.<T>create(newExpression);
return DnfExpression.<T>create().and(newExpression);
}

if (!(existing instanceof DnfExpression)) {
Expand Down
Expand Up @@ -19,7 +19,6 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import org.immutables.criteria.DocumentCriteria;
import org.immutables.criteria.ValueCriteria;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -30,33 +29,32 @@
* Comparing directly values of an attribute.
*
* @param <V> attribute type for which criteria is applied
* @param <C> Criteria self-type, allowing {@code this}-returning methods to avoid needing subclassing
* @param <T> type of the document being evaluated by this criteria
* @param <R> Criteria self-type, allowing {@code this}-returning methods to avoid needing subclassing
*/
public class ObjectCriteria<V, C extends DocumentCriteria<C, T>, T> implements ValueCriteria<C, T> {
public class ObjectCriteria<R extends DocumentCriteria<R>, V> {

final CriteriaCreator<C, T> creator;
private final CriteriaContext<R> context;

ObjectCriteria(CriteriaCreator<C, T> creator) {
this.creator = Preconditions.checkNotNull(creator, "creator");
ObjectCriteria(CriteriaContext<R> context) {
this.context = Preconditions.checkNotNull(context, "context");
}

/**
* Combines existing {@code left} expression with new one
* Use context to create new root DocumentCriteria
*/
protected C create(UnaryOperator<Expression<T>> fn) {
return creator.create(fn);
protected R create(UnaryOperator<Expression<?>> fn) {
return (R) context.create(fn);
}

public C isEqualTo(V value) {
return create(e -> Expressions.<T>call(Operators.EQUAL, e, Expressions.literal(value)));
public R isEqualTo(V value) {
return create(e -> Expressions.call(Operators.EQUAL, e, Expressions.literal(value)));
}

public C isNotEqualTo(V value) {
return create(e -> Expressions.<T>call(Operators.NOT_EQUAL, e, Expressions.literal(value)));
public R isNotEqualTo(V value) {
return create(e -> Expressions.call(Operators.NOT_EQUAL, e, Expressions.literal(value)));
}

public C isIn(V v1, V v2, V ... rest) {
public R isIn(V v1, V v2, V ... rest) {
final List<V> values = new ArrayList<>(2 + rest.length);
values.add(v1);
values.add(v2);
Expand All @@ -65,7 +63,7 @@ public C isIn(V v1, V v2, V ... rest) {
return isIn(values);
}

public C isNotIn(V v1, V v2, V ... rest) {
public R isNotIn(V v1, V v2, V ... rest) {
final List<V> values = new ArrayList<>(2 + rest.length);
values.add(v1);
values.add(v2);
Expand All @@ -74,14 +72,14 @@ public C isNotIn(V v1, V v2, V ... rest) {
return isNotIn(values);
}

public C isIn(Iterable<? super V> values) {
public R isIn(Iterable<? super V> values) {
Preconditions.checkNotNull(values, "values");
return create(e -> Expressions.<T>call(Operators.IN, e, Expressions.literal(ImmutableList.copyOf(values))));
return create(e -> Expressions.call(Operators.IN, e, Expressions.literal(ImmutableList.copyOf(values))));
}

public C isNotIn(Iterable<? super V> values) {
public R isNotIn(Iterable<? super V> values) {
Preconditions.checkNotNull(values, "values");
return create(e -> Expressions.<T>call(Operators.NOT_IN, e, Expressions.literal(ImmutableList.copyOf(values))));
return create(e -> Expressions.call(Operators.NOT_IN, e, Expressions.literal(ImmutableList.copyOf(values))));
}

}
Expand Up @@ -22,18 +22,18 @@
* Criteria for optional attributes.
*/
// TODO what should be the type of V be in ObjectCriteria ? java8.util.Optional<V> or guava.Optional<V> ?
public class OptionalCriteria<V, C extends DocumentCriteria<C, T>, T> extends ObjectCriteria<V, C, T> {
public class OptionalCriteria<R extends DocumentCriteria<R>, V> extends ObjectCriteria<R, V> {

public OptionalCriteria(CriteriaCreator<C, T> creator) {
super(creator);
public OptionalCriteria(CriteriaContext<R> context) {
super(context);
}

public C isPresent() {
return create(e -> Expressions.<T>call(Operators.IS_PRESENT, e));
public R isPresent() {
return create(e -> Expressions.call(Operators.IS_PRESENT, e));
}

public C isAbsent() {
return create(e -> Expressions.<T>call(Operators.IS_ABSENT, e));
public R isAbsent() {
return create(e -> Expressions.call(Operators.IS_ABSENT, e));
}

}
Expand Up @@ -21,29 +21,29 @@
/**
* String specific criterias like {@code isAbsent}, {@code contains} etc.
*/
public class StringCriteria<C extends DocumentCriteria<C, T>, T> extends ComparableCriteria<String, C, T> {
public class StringCriteria<R extends DocumentCriteria<R>> extends ComparableCriteria<R, String> {

public StringCriteria(CriteriaCreator<C, T> creator) {
super(creator);
public StringCriteria(CriteriaContext<R> context) {
super(context);
}

public C isEmpty() {
return create(e -> Expressions.<T>call(Operators.EQUAL, e, Expressions.literal("")));
public R isEmpty() {
return create(e -> Expressions.call(Operators.EQUAL, e, Expressions.literal("")));
}

public C isNotEmpty() {
return create(e -> Expressions.<T>call(Operators.NOT_EQUAL, e, Expressions.literal("")));
public R isNotEmpty() {
return create(e -> Expressions.call(Operators.NOT_EQUAL, e, Expressions.literal("")));
}

public C contains(CharSequence other) {
public R contains(CharSequence other) {
throw new UnsupportedOperationException();
}

public C startsWith(CharSequence prefix) {
public R startsWith(CharSequence prefix) {
throw new UnsupportedOperationException();
}

public C endsWith(CharSequence suffix) {
public R endsWith(CharSequence suffix) {
throw new UnsupportedOperationException();
}

Expand Down

0 comments on commit bc21046

Please sign in to comment.