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

hm.binkley.util.function.Matching Maven / Gradle / Ivy

The newest version!
package hm.binkley.util.function;

import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static java.util.Arrays.asList;
import static lombok.AccessLevel.PRIVATE;

/**
 * {@code Matching} represents Pattern
 * Matching in Java as a function production an optional.  Example: 
 * asList(0, 1, 2, 3, 13, 14, null, -1).stream().
 *         peek(n -> out.print(format("%d -> ", n))).
 *         map(matching(Integer.class, Object.class).
 *             when(Objects::isNull).then(n -> "!").
 *             when(is(0)).then(nil()).
 *             when(is(1)).then("one").
 *             when(is(13)).then(() -> "unlucky").
 *             when(is(14)).then(printIt()).
 *             when(even()).then(scaleBy(3)).
 *             when(gt(2)).then(dec()).
 *             none().then("no match")).
 *         map(MatchingTest::toString).
 *         forEach(out::println);
*

* NB — There is no way to distinguish from an empty optional if * there was no match, or if a match mapped the input to {@code null}, without * use of a {@link When#then(Object) sentinel value} or {@link * When#thenThrow(Supplier) thrown exception}. *

* NB — There is no formal destructuring, but this can * be simulated in the {@code Predicate} to {@link #when(Predicate) when}. * * @param the input type to match against * @param the output type of a matched pattern * * @author Brian Oxley */ @NoArgsConstructor(access = PRIVATE) public final class Matching implements Function> { private final Collection cases = new ArrayList<>(); /** * Begins pattern matching with a new pattern matcher. * * @param inType the input type token, never {@code null} * @param outType the output type token, never {@code null} * @param the input type to match against * @param the output type of a matched pattern * * @return the pattern matcher, never {@code null} * * @todo Avoid the type tokens */ public static Matching matching(final Class inType, final Class outType) { return new Matching<>(); } /** * Begins a when/then pair. * * @param when the pattern matching test, never {@code null} * * @return the pattern continuance, never {@code null} */ public When when(final Predicate when) { return new When(when); } /** * Begins a default when/then pair, always placed last in * the list of cases (evaluates no cases after this one). * * @return then pattern continuance, never {@code null} */ public When none() { return when(o -> true); } /** * Evaluates the pattern matching. * * @param in the input to match against, possibly {@code null} * * @return the match result (empty if no match), never {@code null} */ @Override public Optional apply(final T in) { return cases.stream(). filter(c -> c.p.test(in)). findFirst(). map(c -> c.q.apply(in)); } @RequiredArgsConstructor(access = PRIVATE) public final class When { /** * Number of frames to discard when creating an exception for a match. * Very sensitive to implementation. This aids in understanding stack * traces from matching, discarding internal machinery and leaving the * actual throwing call at the top of the stack. */ private static final int N = 7; private final Predicate when; /** * Ends a when/then pair, evaluating then against the input * if matched. * * @param then the pattern matching function, never {@code null} * * @return the pattern matcher, never {@code null} */ public Matching then( final Function then) { cases.add(new Case(when, then)); return Matching.this; } /** * Ends a when/then pair, returning then if matched. * * @param then the pattern matching value, possibly {@code null} * * @return the pattern matcher, never {@code null} */ public Matching then(final U then) { cases.add(new Case(when, x -> then)); return Matching.this; } /** * Ends a when/then pair, evaluating then independent of * supplier if matched. * * @param then the pattern matching supplier, never {@code null} * * @return the pattern matcher, never {@code null} */ public Matching then(final Supplier then) { cases.add(new Case(when, x -> then.get())); return Matching.this; } /** * Ends a when/then pair, evaluating then to {@code null} * if matched. * * @param then the input consumer, never {@code null} * * @return the pattern matcher, never {@code null} */ public Matching then(final Consumer then) { cases.add(new Case(when, o -> { then.accept(o); return null; })); return Matching.this; } /** * Ends a when/then pair, evaluating then independent of * supplier and throwing the new exception if matched. * * @param then the pattern matching exception supplier, never {@code * null} * * @return the pattern matcher, never {@code null} */ public Matching thenThrow( final Supplier then) { cases.add(new Case(when, x -> { final RuntimeException e = then.get(); final List stack = asList( e.getStackTrace()); e.setStackTrace(stack.subList(N, stack.size()). toArray(new StackTraceElement[stack.size() - N])); throw e; })); return Matching.this; } } @RequiredArgsConstructor(access = PRIVATE) private final class Case { private final Predicate p; private final Function q; } }