Skip to content

Commit

Permalink
Add syntax sugar to Optional matchers with is(Optional) / isNot(Optio…
Browse files Browse the repository at this point in the history
…nal) methods

Helpful for nullable arguments which get converted to isPresent / isAbsent
equivalent depending on optional value.

value == Optional.empty() changed to isAbsent()
value == Optional.of(123) changed to is(123)

This allows making calling API like:

person.name.is(Optional.ofNullable(nullableName));
  • Loading branch information
asereda-gs committed May 2, 2020
1 parent ba79f0e commit 0c2d47e
Show file tree
Hide file tree
Showing 21 changed files with 228 additions and 45 deletions.
Expand Up @@ -34,11 +34,21 @@
*/
public interface ObjectMatcher<R, V> extends Matcher {

/**
* Equivalent to {@code this == value} ({@code value} can't be null)
* @throws NullPointerException if value argument is null
*/
default R is(V value) {
Objects.requireNonNull(value,"value");
return Matchers.extract(this).applyAndCreateRoot(e -> Expressions.call(Operators.EQUAL, e, Expressions.constant(value)));
}

/**
* Equivalent to {@code this != value} ({@code value} can't be null)
* @throws NullPointerException if value argument is null
*/
default R isNot(V value) {
Objects.requireNonNull(value,"value");
return Matchers.extract(this).applyAndCreateRoot(e -> Expressions.call(Operators.NOT_EQUAL, e, Expressions.constant(value)));
}

Expand All @@ -60,6 +70,9 @@ default R notIn(V v1, V v2, V ... rest) {
return notIn(values);
}

/**
* Equivalent to {@code this in $values}
*/
default R in(Iterable<? extends V> values) {
Objects.requireNonNull(values, "values");
return Matchers.extract(this).applyAndCreateRoot(e -> Expressions.call(Operators.IN, e, Expressions.constant(ImmutableList.copyOf(values))));
Expand Down
Expand Up @@ -20,9 +20,9 @@
import java.util.Optional;

/**
* Intersection type between {@link OptionalMatcher} and {@link NumberMatcher}.
* Intersection type between {@link OptionalObjectMatcher} and {@link NumberMatcher}.
*
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalMatcher}
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalObjectMatcher}
* on long expressions with many optional elements.
*
* @param <R> root criteria type
Expand Down
Expand Up @@ -21,9 +21,9 @@
import java.util.Optional;

/**
* Intersection type between {@link OptionalMatcher} and {@link NumberMatcher}.
* Intersection type between {@link OptionalObjectMatcher} and {@link NumberMatcher}.
*
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalMatcher}
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalObjectMatcher}
* on long expressions with many optional elements.
*
* @param <R> root criteria type
Expand Down
Expand Up @@ -16,13 +16,30 @@
package org.immutables.criteria.matcher;


import java.util.Objects;
import java.util.Optional;

/**
* Intersection type between {@link OptionalMatcher} and {@link BooleanMatcher}
* Intersection type between {@link OptionalValueMatcher} and {@link BooleanMatcher}
*
* @param <R> root criteria type
*/
public interface OptionalBooleanMatcher<R> extends BooleanMatcher<R>, PresentAbsentMatcher<R> {

/**
* Match current boolean attribute given an optional boolean parameter. If optional is
* empty, matching is equivalent to {@link #isAbsent()} otherwise standard
* {@link #is(boolean)} matching is used.
*
* @param optional argument to match with
* @throws NullPointerException if argument is null
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
default R is(Optional<Boolean> optional) {
Objects.requireNonNull(optional, "optional");
return optional.map(this::is).orElseGet(this::isAbsent);
}

interface Self extends Template<Self, Void> {}

interface Template<R, P> extends OptionalBooleanMatcher<R>, WithMatcher<R, Self>, NotMatcher<R, Self>, Projection<P>, Aggregation.Count {}
Expand Down
Expand Up @@ -19,14 +19,14 @@
import java.util.Optional;

/**
* Intersection type between {@link OptionalMatcher} and {@link ComparableMatcher}.
* Intersection type between {@link OptionalObjectMatcher} and {@link ComparableMatcher}.
*
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalMatcher}
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalObjectMatcher}
* on long expressions with many optional elements.
*
* @param <R> root criteria type
*/
public interface OptionalComparableMatcher<R, V extends Comparable<? super V>> extends ComparableMatcher<R, V>, PresentAbsentMatcher<R> {
public interface OptionalComparableMatcher<R, V extends Comparable<? super V>> extends ComparableMatcher<R, V>, OptionalValueMatcher<R, V> {

/**
* Self-type for this matcher
Expand Down
Expand Up @@ -16,18 +16,25 @@

package org.immutables.criteria.matcher;

import java.util.Objects;
import java.util.OptionalDouble;

/**
* Intersection type between {@link OptionalMatcher} and {@link NumberMatcher}.
* Intersection type between {@link OptionalObjectMatcher} and {@link NumberMatcher}.
*
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalMatcher}
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalObjectMatcher}
* on long expressions with many optional elements.
*
* @param <R> root criteria type
*/
public interface OptionalDoubleMatcher<R> extends OptionalNumberMatcher<R, Double> {

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
default R is(OptionalDouble optional) {
Objects.requireNonNull(optional, "optional");
return optional.isPresent() ? is(optional.getAsDouble()) : isAbsent();
}

/**
* Self-type for this matcher
*/
Expand Down
Expand Up @@ -16,20 +16,27 @@

package org.immutables.criteria.matcher;

import java.util.Objects;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;

/**
* Intersection type between {@link OptionalMatcher} and {@link NumberMatcher}.
* Intersection type between {@link OptionalObjectMatcher} and {@link NumberMatcher}.
*
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalMatcher}
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalObjectMatcher}
* on long expressions with many optional elements.
*
* @param <R> root criteria type
*/
public interface OptionalIntegerMatcher<R> extends OptionalNumberMatcher<R, Integer> {

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
default R is(OptionalInt optional) {
Objects.requireNonNull(optional, "optional");
return optional.isPresent() ? is(optional.getAsInt()) : isAbsent();
}

/**
* Self-type for this matcher
*/
Expand Down
Expand Up @@ -16,19 +16,26 @@

package org.immutables.criteria.matcher;

import java.util.Objects;
import java.util.OptionalDouble;
import java.util.OptionalLong;

/**
* Intersection type between {@link OptionalMatcher} and {@link NumberMatcher}.
* Intersection type between {@link OptionalObjectMatcher} and {@link NumberMatcher}.
*
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalMatcher}
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalObjectMatcher}
* on long expressions with many optional elements.
*
* @param <R> root criteria type
*/
public interface OptionalLongMatcher<R> extends OptionalNumberMatcher<R, Long> {

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
default R is(OptionalLong optional) {
Objects.requireNonNull(optional, "optional");
return optional.isPresent() ? is(optional.getAsLong()) : isAbsent();
}

/**
* Self-type for this matcher
*/
Expand Down
Expand Up @@ -20,14 +20,14 @@
import java.util.OptionalDouble;

/**
* Intersection type between {@link OptionalMatcher} and {@link NumberMatcher}.
* Intersection type between {@link OptionalObjectMatcher} and {@link NumberMatcher}.
*
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalMatcher}
* <p>Syntax sugar to avoid chaining {@code value()} method from {@link OptionalObjectMatcher}
* on long expressions with many optional elements.
*
* @param <R> root criteria type
*/
public interface OptionalNumberMatcher<R, V extends Number & Comparable<? super V>> extends NumberMatcher<R, V>, PresentAbsentMatcher<R> {
public interface OptionalNumberMatcher<R, V extends Number & Comparable<? super V>> extends NumberMatcher<R, V>, OptionalValueMatcher<R, V> {

/**
* Self-type for this matcher
Expand Down
Expand Up @@ -17,9 +17,9 @@
package org.immutables.criteria.matcher;

/**
* Matcher for optional attributes
* Matcher for optional (non-scalar) attributes
*/
public interface OptionalMatcher<R, S> extends PresentAbsentMatcher<R>, Matcher {
public interface OptionalObjectMatcher<R, S> extends PresentAbsentMatcher<R>, Matcher {

/**
* Apply context-specific matcher if value is present
Expand All @@ -34,7 +34,7 @@ default S value() {
*/
interface Self<S> extends Template<Self<S>, S, Void>, Disjunction<Self<S>> {}

interface Template<R, S, P> extends OptionalMatcher<R, S>, Projection<P>, Aggregation.Count {}
interface Template<R, S, P> extends OptionalObjectMatcher<R, S>, Projection<P>, Aggregation.Count {}

@SuppressWarnings("unchecked")
static <R> CriteriaCreator<R> creator() {
Expand Down
Expand Up @@ -19,10 +19,10 @@
import java.util.Optional;

/**
* Intersection type between {@link OptionalMatcher} and {@link StringMatcher}
* Intersection type between {@link OptionalValueMatcher} and {@link StringMatcher}
* @param <R> root criteria type
*/
public interface OptionalStringMatcher<R> extends StringMatcher<R>, PresentAbsentMatcher<R> {
public interface OptionalStringMatcher<R> extends StringMatcher<R>, OptionalValueMatcher<R, String> {

interface Self extends Template<Self, Void>, Disjunction<OptionalStringMatcher<Self>> {}

Expand Down
@@ -0,0 +1,61 @@
/*
* Copyright 2020 Immutables Authors and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.immutables.criteria.matcher;

import java.util.Objects;
import java.util.Optional;

/**
* Optional matcher for scalar (string, int, comparable etc.) attributes.
* In addition to standard {@link ObjectMatcher} contract offers similar methods but with
* {@link Optional} arguments. Depending on optional value (empty / present) matcher will
* delegate to {@link #isPresent()}, {@link #isAbsent()} or {@link #is(Object)}
*/
public interface OptionalValueMatcher<R, V> extends ObjectMatcher<R, V>, PresentAbsentMatcher<R> {

/**
* Match current attribute given an optional parameter. If optional is
* empty, matching is equivalent to {@link #isAbsent()} otherwise standard
* {@link #is(Object)} matching is used.
*
* @param optional argument to match with
* @throws NullPointerException if argument is null
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
default R is(Optional<? extends V> optional) {
Objects.requireNonNull(optional, "optional");
return optional.map(this::is).orElseGet(this::isAbsent);
}

/**
* Match current attribute given an optional parameter. If optional is
* empty, matching is equivalent to {@link #isPresent()} ()} otherwise standard
* {@link #isNot(Object)} matching is used.
*
* <p><strong>Note</strong> Different backends might treat null (missing/unknown) value differently. Negations
* might or might not return missing value.
* For SQL there is <a href="https://modern-sql.com/concept/three-valued-logic">Three-Valued-Logic</a>.
* while Mongo is two-valued.
* @throws NullPointerException if argument is null
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
default R isNot(Optional<? extends V> optional) {
Objects.requireNonNull(optional, "optional");
return optional.map(this::isNot).orElseGet(this::isPresent);
}

}
Expand Up @@ -80,10 +80,10 @@ public void timeZone() {
assertAttribute("timeZone",
"org.immutables.criteria.matcher.ObjectMatcher.Template<R,java.util.TimeZone>");
assertAttribute("optionalTimeZone",
"org.immutables.criteria.matcher.OptionalMatcher.Template<R,org.immutables.criteria.matcher.ObjectMatcher.Template<R,java.util.TimeZone>,java.util.Optional<java.util.TimeZone>>");
"org.immutables.criteria.matcher.OptionalObjectMatcher.Template<R,org.immutables.criteria.matcher.ObjectMatcher.Template<R,java.util.TimeZone>,java.util.Optional<java.util.TimeZone>>");
checkCreator("optionalTimeZone").contains("ObjectMatcher.creator()");
assertAttribute("nullableTimeZone",
"org.immutables.criteria.matcher.OptionalMatcher.Template<R,org.immutables.criteria.matcher.ObjectMatcher.Template<R,java.util.TimeZone>,java.util.TimeZone>");
"org.immutables.criteria.matcher.OptionalObjectMatcher.Template<R,org.immutables.criteria.matcher.ObjectMatcher.Template<R,java.util.TimeZone>,java.util.TimeZone>");
checkCreator("nullableTimeZone").contains("ObjectMatcher.creator()");
checkCreator("listTimeZone").contains("ObjectMatcher.creator()");
checkCreator("arrayTimeZone").contains("ObjectMatcher.creator()");
Expand All @@ -99,15 +99,15 @@ public void array() {
@Test
public void wierd() {
assertAttribute("weird1",
"org.immutables.criteria.matcher.OptionalMatcher.Template<R,org.immutables.criteria.matcher.OptionalStringMatcher.Template<R,java.util.Optional<java.lang.String>>,java.util.Optional<java.util.Optional<java.lang.String>>>");
"org.immutables.criteria.matcher.OptionalObjectMatcher.Template<R,org.immutables.criteria.matcher.OptionalStringMatcher.Template<R,java.util.Optional<java.lang.String>>,java.util.Optional<java.util.Optional<java.lang.String>>>");
// Optional<List<String>> weird2();
assertAttribute("weird2",
"org.immutables.criteria.matcher.OptionalMatcher.Template<R,org.immutables.criteria.matcher.IterableMatcher.Template<R,org.immutables.criteria.matcher.StringMatcher.Template<R>,java.lang.String,java.util.List<java.lang.String>>,java.util.Optional<java.util.List<java.lang.String>>>");
"org.immutables.criteria.matcher.OptionalObjectMatcher.Template<R,org.immutables.criteria.matcher.IterableMatcher.Template<R,org.immutables.criteria.matcher.StringMatcher.Template<R>,java.lang.String,java.util.List<java.lang.String>>,java.util.Optional<java.util.List<java.lang.String>>>");
// List<Optional<String>> weird3();
assertAttribute("weird3",
"org.immutables.criteria.matcher.IterableMatcher.Template<R,org.immutables.criteria.matcher.OptionalStringMatcher.Template<R,java.util.Optional<java.lang.String>>,java.util.Optional<java.lang.String>,java.util.List<java.util.Optional<java.lang.String>>>");
assertAttribute("weird4",
"org.immutables.criteria.matcher.OptionalMatcher.Template<R,org.immutables.criteria.matcher.OptionalIntegerMatcher.Template<R,java.util.OptionalInt>,java.util.Optional<java.util.OptionalInt>>");
"org.immutables.criteria.matcher.OptionalObjectMatcher.Template<R,org.immutables.criteria.matcher.OptionalIntegerMatcher.Template<R,java.util.OptionalInt>,java.util.Optional<java.util.OptionalInt>>");
}

@Test
Expand Down Expand Up @@ -208,16 +208,16 @@ public void havingCriteria() {


assertAttribute("nullableFoo",
"org.immutables.criteria.matcher.OptionalMatcher.Template<R,org.immutables.criteria.processor.FooCriteriaTemplate<R>,org.immutables.criteria.processor.CriteriaModelProcessorTest.Foo>");
"org.immutables.criteria.matcher.OptionalObjectMatcher.Template<R,org.immutables.criteria.processor.FooCriteriaTemplate<R>,org.immutables.criteria.processor.CriteriaModelProcessorTest.Foo>");

checkCreator("nullableFoo").contains("FooCriteria.creator()");
checkCreator("nullableFoo").contains("OptionalMatcher.creator()");
checkCreator("nullableFoo").contains("OptionalObjectMatcher.creator()");

assertAttribute("optionalFoo",
"org.immutables.criteria.matcher.OptionalMatcher.Template<R,org.immutables.criteria.processor.FooCriteriaTemplate<R>,java.util.Optional<org.immutables.criteria.processor.CriteriaModelProcessorTest.Foo>>");
"org.immutables.criteria.matcher.OptionalObjectMatcher.Template<R,org.immutables.criteria.processor.FooCriteriaTemplate<R>,java.util.Optional<org.immutables.criteria.processor.CriteriaModelProcessorTest.Foo>>");

checkCreator("optionalFoo").contains("FooCriteria.creator()");
checkCreator("optionalFoo").contains("OptionalMatcher.creator()");
checkCreator("optionalFoo").contains("OptionalObjectMatcher.creator()");

assertAttribute("listFoo",
"org.immutables.criteria.matcher.IterableMatcher.Template<R,org.immutables.criteria.processor.FooCriteriaTemplate<R>,org.immutables.criteria.processor.CriteriaModelProcessorTest.Foo,java.util.List<org.immutables.criteria.processor.CriteriaModelProcessorTest.Foo>>");
Expand Down
Expand Up @@ -82,6 +82,11 @@ protected void optional() {
ids(holder.optional.isTrue()).hasContentInAnyOrder("id2");
ids(holder.optional.is(false)).hasContentInAnyOrder("id1");
ids(holder.optional.isFalse()).hasContentInAnyOrder("id1");

// using OptionalValue matcher API
ids(holder.optional.is(Optional.empty())).isOf("id3");
ids(holder.optional.is(Optional.of(true))).isOf("id2");
ids(holder.optional.is(Optional.of(false))).isOf("id1");
}

@Test
Expand All @@ -96,6 +101,11 @@ void nullable() {
ids(holder.nullable.isTrue()).hasContentInAnyOrder("id2");
ids(holder.nullable.is(false)).hasContentInAnyOrder("id1");
ids(holder.nullable.isFalse()).hasContentInAnyOrder("id1");

// using OptionalValue matcher API
ids(holder.nullable.is(Optional.empty())).isOf("id3");
ids(holder.nullable.is(Optional.of(true))).isOf("id2");
ids(holder.nullable.is(Optional.of(false))).isOf("id1");
}

@Test
Expand Down

0 comments on commit 0c2d47e

Please sign in to comment.