com.aol.cyclops.matcher.Case Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cyclops-pattern-matching Show documentation
Show all versions of cyclops-pattern-matching Show documentation
Advanced pattern matching for Java 8
package com.aol.cyclops.matcher;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.function.Predicate;
import com.aol.cyclops.closures.immutable.LazyImmutable;
import com.aol.cyclops.matcher.builders.ADTPredicateBuilder;
/**
* An interface / trait for building functionally compositional pattern matching cases
*
* Consists of a Predicate and a Function
* When match is called, if the predicate holds the function is executed
*
*
* @author johnmcclean
*
* @param Input type for predicate and function (action)
* @param Return type for function (action) which is executed if the predicate tests positive
* @param Type of Function - cyclops pattern matching builders use ActionWithReturn which is serialisable and retains type info
*/
public interface Case> {
/**
* @return true is this an 'empty' case
*/
public boolean isEmpty();
/**
* @return Pattern for this case
*/
public Two,X> get();
/**
* @return Predicate for this case
*/
default Predicate getPredicate(){
return get().v1;
}
/**
* @return Action (Function) for this case
*/
default X getAction(){
return get().v2;
}
/**
* @return A new case with the predicate negated
*
* {@code
* Case.of((Integer num) -> num >100,(Integer num) -> num * 10).negate();
*
* }
*
* Results in a case that will multiply numbers 100 or less by 10
*
*/
default Case negate(){
return map(t2-> Two.tuple(t2.v1.negate(),t2.v2));
}
/**
* @param action New action to replace current one
* @return A news case that inverts the predicate and replaces the current action with the supplied one
*
*
* Case.of((Integer num) -> num >100,(Integer num) -> num * 10).negate(num -> num * 5);
*
*
*
* Results in a case that will multiply numbers 100 or less by 5
*/
default Case negate(X action){
return map(t2-> Two.tuple(t2.v1.negate(),action));
}
/**
* Filter this case with the supplied predicate
*
* Some options here are to filter based on
* success of predicate execution
* return values from function execution
* if using ActionWithReturn for function instances on Generic parameter types
* Custom function types with additional info can be used with Case
*
*
*
* empty = Case.of(t->true,input->10).filter(p->false);
* assertThat(empty,instanceOf(EmptyCase.class));
*
* ActionWithReturn<String,Integer> act = hello ->10;
* caze = Case.of(t->true, act);
*
*
*
*
*
*
* @param predicate
* @return
*/
default Case filter(Predicate,X>> predicate){
return predicate.test(get()) ? this : empty();
}
/**
* Allows the predicate to be replaced with one returned from the supplied function
*
* E.g. switching off a predicate
*
* case1 =Case.of(input->true,input->input+10);
* assertThat(case1.mapPredicate(p-> t->false).match(100).isPresent(),is(false));
*
*
* @param mapper Function that supplies the new predicate
* @return A new Case instance with a new Predicate
*/
default Case mapPredicate(Function,Predicate> mapper){
return this.map(t2-> Two.tuple(mapper.apply(t2.v1),t2.v2));
}
/**
* Allows the current function to be replaced by another
*
* case1 =Case.of(input->true,input->input+10);
* assertThat(case1.mapFunction(fn-> input->input+20).match(100).get(),is(120));
*
* Returns 100+20 rather than 100+10
*
* @param mapper Function that supplies the new function
* @return A new Case instance with a new function
*/
default > Case mapFunction(Function,X1> mapper){
return this.map(t2-> Two.tuple(t2.v1,mapper.apply(t2.v2)));
}
/**
* Allows both the predicate and function in the current case to be replaced in a new Case
*
* case1 =Case.of(input->false,input->input+10);
* Tuple2<Predicate<Integer>,Function<Integer,Integer>> tuple = Tuple.tuple( t->true,(Integer input)->input+20);
assertThat(case1.map(tuple2 -> tuple).match(100).get(),is(120));
*
*
* @param mapper Function that generates the new predicate and action (function)
* @return New case with a (potentially) new predicate and action
*/
default > Case map(Function,X>,Two,X1>> mapper){
return Case.of(mapper.apply(get()));
}
/**
* Create a new Case from the supplied Function
*
*
* case1 =Case.of(input->false,input->input+10);
* assertThat(case1.flatMap(tuple2 -> Case.of(tuple2.v1,(Integer input)->input+20)).match(100).get(),is(120));
*
*
* @param mapper Function that creates a new Case
* @return new Case instance created
*/
default > Case flatMap(Function,X>,Case> mapper){
return mapper.apply(get());
}
/**
* Provide a Case that will be run only if this one matches successfully
* The result of the current case will be the input into the supplied case
* Both cases have to be successful to return a result from match
*
*
* case1 =Case.of(input->false,input->input+10);
* assertThat(case1.andThen(Case.of(input->true,input->input+1)).match(100).get(),is(111));
*
*
* @param after Case that will be run after this one, if it matches successfully
* @return New Case which chains current case and the supplied one
*/
default Case> andThen(Case> after){
return after.compose(this);
}
/**
* Add a set of Cases that will be run if this case matches successfull
*
* @param after Cases to run if this case matches
* @return New Case which chains current case and the supplied cases
*/
default Case> andThen(Cases> after){
final LazyImmutable> var = new LazyImmutable<>();
return andThen(Case.of(t-> var.setOnce(after.match(t)).get().isPresent(), t-> var.get().get()));
}
/**
* Provide a Case that will be executed before the current one. The current case will only be attempted
* if the supplied case matches.
*
* {@code
* case1 =Case.of(input->false,input->input+10);
* assertThat(case1.compose(Case.of((Integer input)->true,input->input*2)).match(100).get(),is(210))
* }
*
* (100*2)+10=210
*
* @param before Case to be run before this one
* @return New Case which chains the supplied case and the current one
*/
default Case> compose(Case> before){
final LazyImmutable value = new LazyImmutable<>();
Predicate predicate =t-> {
final boolean passed;
if(before.get().v1.test(t)){
passed=true;
value.setOnce(before.get().v2.apply(t));
}else
passed= false;
return passed && get().v1.test(value.get());
};
return Case.>of(predicate, input -> get().v2.apply(value.get()));
}
/**
* Provide a Case that will be executed before the current one. The function from the supplied Case will be executed
* first and it;s output will be provided to the function of the current case - if either predicate holds.
*
*
* case1 =Case.of(input->false,input->input+10);
* assertThat(case1.composeOr(Case.of((Integer input)->false,input->input*2)).match(100).get(),is(210));
*
*
*
* @param before Case to be run before this one
* @return New Case which chains the supplied case and the current one
*/
default Case> composeOr(Case> before){
return Case.>of(t-> before.get().v1.test(t) || get().v1.test(before.get().v2.apply(t)),
input -> get().v2.apply(before.get().v2.apply(input)));
}
/**
* Creates a new Case that will execute the supplied function before current function if current predicate holds.
* The output of this function will be provided to the function in the current case.
*
*
* case1 =Case.of(input->false,input->input+10);
* assertThat(case1.composeFunction((Integer input)->input*2).match(100).get(),is(210));
*
*
*
* @param before Function to execute before current function
* @return New case which chains the supplied function before the current function
*/
default Case> composeFunction(Function before){
return this.compose(Case.>of(t->true,before));
}
/**
* Creates a new Case that will execute the supplied function after current function if current predicate holds.
* The output of the current function will be provided as input to the supplied function.
*
*
* case1 =Case.of(input->false,input->input+10);
* assertThat(case1.andThenFunction(input->input*2).match(100).get(),is(220));
*
*
* @param after Function to execute after the current function
* @return New case which chains the supplied function after the current function
*/
default Case> andThenFunction(Function after){
return this.andThen(Case.>of(r->true,after));
}
/**
* Create a new Case that will loosen the current predicate.
* Either predicate can hold in order for the Case to pass.
*
*
* case1 =Case.of(input->true,input->input+10);
* offCase = case1.mapPredicate(p-> p.negate());
* assertThat(offCase.or(t->true).match(100).get(),is(110));
*
*
*
* @param or Predicate that will be or'd with the current predicate
* @return New Case where the predicate is the supplied predicate or the current predicate.
*/
default Case> or(Predicate or){
return composeOr(Case.of(or,Function.identity()));
}
/**
* Syntax sugar for composeOr
* @see #composeOr
*
* Provide a Case that will be executed before the current one. The function from the supplied Case will be executed
* first and it;s output will be provided to the function of the current case - if either predicate holds.
*
*
* case1 =Case.of(input->false,input->input+10);
* assertThat(case1.composeOr(Case.of((Integer input)->false,input->input*2)).match(100).get(),is(210));
*
*
* @param or Predicate for before case
* @param fn Function for before case
* @return New Case which chains the supplied case and the current one
*/
default Case> or(Predicate or, Function fn){
return composeOr(Case.of(or,fn));
}
/**
* Create a new case which ands the supplied predicate with the current predicate.
* The supplied predicate will be tested before existing predicate
*
* {@code
* assertThat(case1.and(p->false).match(100).isPresent(),is(false));
*
* assertThat(case1.and(p->true).match(100).isPresent(),is(true));
*
* }
*
* @param and New Predicate to be and'd with current
*
* @return New case instance which and's current predicate with supplied
*/
default Case> and(Predicate and){
return compose((Case)Case.of(and,Function.identity()));
}
/**
* Add a guard that assures input is of specified type. Note that this can't change the type supported by this case,
* but will allow this case to be used in a mixed type Cases environment
* e.g.
*
*
* case1 =Case.of(input->true,input->input+10);
* assertThat(case1.andOfType(Integer.class).match(100).isPresent(),is(true));
* assertThat(((Case)case1).andOfType(String.class).match(100).isPresent(),is(false));
*
*
* @param and Class to check type against
* @return New case with Class type guard inserted before current predicate
*/
default Case> andOfType(Class and){
return compose((Case)Case.of(input -> input.getClass().isAssignableFrom(and),Function.identity()));
}
/**
* Add a guard that assures input is of specified type. Note that this can't change the type supported by this case,
* but will allow this case to be used in a mixed type Cases environment
* e.g.
*
*
* case1 =Case.of(input->true,input->input+10);
* assertThat(((Case)case1).andOfValue(5).match(100).isPresent(),is(false));
* assertThat(case1.andOfValue(100).match(100).isPresent(),is(true));
*
*
* @param and Class to check type against
* @return New case with Class type guard inserted before current predicate
*/
default Case> andOfValue(T and){
return compose((Case)Case.of(input -> Objects.equals(input,and),Function.identity()));
}
/**
* Insert a guard that decomposes input values and compares against the supplied values
* Recursive decomposition is possible via Predicates.type and Predicates.with methods
* @see Predicates#type
* @see Predicates#with
*
* {@code
* val case2 = Case.of((Person p)->p.age>18,p->p.name + " can vote");
* assertThat(case2.andWithValues(__,__,Predicates.with(__,__,"Ireland")).match(new Person("bob",19,new Address(10,"dublin","Ireland"))).isPresent(),is(true));
* assertThat(case2.andWithValues(__,__,with(__,__,"Ireland")).match(new Person("bob",17,new Address(10,"dublin","Ireland"))).isPresent(),is(false));
*
*
* \@Value static final class Person implements Decomposable{ String name; int age; Address address; }
* \@Value static final class Address implements Decomposable { int number; String city; String country;}
*
* }
*
*
* @param with values to compare to - or predicates or hamcrest matchers
* @return New case with test against decomposed values inserted as a guard
*/
default Case> andWithValues(Object... with){
return compose((Case)Case.of(new ADTPredicateBuilder(Object.class).hasValues(with),Function.identity()));
}
/**
* Compose a new Case which executes the Predicate and function supplied before the current predicate
* and function.
*
* {@code
* e.g. match(100)
* new Predicate passes (100) -> apply new function (100) returns 20
* -> predicate from current case recieves (20) passes
* -> apply function from current case recieves (20)
* }
* both predicates must pass or Optional.empty() is returned from match.
*
* {@code
* case1 =Case.of(input->true,input->input+10);
*
* assertThat(case1.composeAnd(p->false,(Integer input)->input*2).match(100).isPresent(),is(false));
*
* assertThat(case1.composeAnd(p->true,(Integer input)->input*2).match(100).get(),is(210));
*
* }
* @param and Predicate to be and'd with current predicate
* @param before
* @return
*/
default Case> composeAnd(Predicate and, Function before){
return compose(Case.of(and,before));
}
/**
* @return true if this not an EmptyCase
*/
default boolean isNotEmpty(){
return !this.isEmpty();
}
/**
* Match against the supplied value.
* Value will be passed into the current predicate
* If it passes / holds, value will be passed to the current function.
* The result of function application will be returned wrapped in an Optional.
* If the predicate does not hold, Optional.empty() is returned.
*
* @param value To match against
* @return Optional.empty if doesn't match, result of the application of current function if it does wrapped in an Optional
*/
default Optional match(T value){
if(get().v1.test(value))
return Optional.of(get().v2.apply(value));
return Optional.empty();
}
/**
* Similar to Match, but executed asynchonously on supplied Executor.
*
* @see #match
*
* Match against the supplied value.
* Value will be passed into the current predicate
* If it passes / holds, value will be passed to the current function.
* The result of function application will be returned wrapped in an Optional.
* If the predicate does not hold, Optional.empty() is returned.
*
* @param executor Executor to execute matching on
* @param value Value to match against
* @return A CompletableFuture that will eventual contain an Optional.empty if doesn't match, result of the application of current function if it does
*/
default CompletableFuture> matchAsync(Executor executor, T value){
return CompletableFuture.supplyAsync(()->match(value),executor);
}
/**
* Construct an instance of Case from supplied predicate and action
*
* @param predicate That will be used to match
* @param action Function that is executed on succesful match
* @return New Case instance
*/
public static > Case of(Predicate predicate,X action){
return new ActiveCase(Two.tuple(predicate,action));
}
/**
* Construct an instance of Case from supplied Tuple of predicate and action
*
* @param pattern containing the predicate that will be used to match and the function that is executed on succesful match
* @return New Case instance
*/
public static > Case of(Two,X> pattern){
return new ActiveCase<>(pattern);
}
public static final Case empty = new EmptyCase();
/**
* @return EmptyCase
*/
public static > Case empty(){
return empty;
}
}