All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aol.cyclops.matcher.Case Maven / Gradle / Ivy

There is a newer version: 7.3.1
Show newest version
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-&gt;false,input-&gt;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-&gt;false,input-&gt;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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy