Helper class for dealing with 2 Optionals simultaneously
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dgronau; | |
import java.util.Objects; | |
import java.util.Optional; | |
import java.util.function.*; | |
public class Optional2<A, B> { | |
private final Optional<A> first; | |
private final Optional<B> second; | |
private Optional2(Optional<A> first, Optional<B> second) { | |
this.first = Objects.requireNonNull(first); | |
this.second = Objects.requireNonNull(second); | |
} | |
public static <A, B> Optional2<A, B> empty() { | |
return of(Optional.empty(), Optional.empty()); | |
} | |
public static <A, B> Optional2<A, B> of(Optional<A> first, Optional<B> second) { | |
return new Optional2<>(first, second); | |
} | |
public static <A, B> Optional2<A, B> ofNullable(A a, B b) { | |
return new Optional2<>(Optional.ofNullable(a), Optional.ofNullable(b)); | |
} | |
public static <A> Optional<A> unify(Optional2<A, A> optional2, BinaryOperator<A> op) { | |
return unify(optional2.first, optional2.second, op); | |
} | |
public static <A> Optional<A> unify(Optional<A> first, Optional<A> second, BinaryOperator<A> op) { | |
return first.map( | |
a -> second.map( | |
b -> op.apply(a, b)).orElse(a)) | |
.map(Optional::of) | |
.orElse(second); | |
} | |
public Optional<A> first() { | |
return first; | |
} | |
public Optional<B> second() { | |
return second; | |
} | |
public Optional2<B, A> swap() { | |
return of(second, first); | |
} | |
public <C> Optional<C> map(BiFunction<A, B, C> fn) { | |
return first.flatMap( | |
a -> second.map( | |
b -> fn.apply(a, b))); | |
} | |
public <C> Optional2<C, B> mapFirst(BiFunction<A, B, C> fn) { | |
return of(map(fn), second); | |
} | |
public <C> Optional2<C, B> mapFirst(Function<A, C> fn) { | |
return of(first.map(fn), second); | |
} | |
public <C> Optional2<A, C> mapSecond(BiFunction<A, B, C> fn) { | |
return of(first, map(fn)); | |
} | |
public <C> Optional2<A, C> mapSecond(Function<B, C> fn) { | |
return of(first, second.map(fn)); | |
} | |
public <C> Optional<C> flatMap(BiFunction<A, B, Optional<C>> fn) { | |
return first.flatMap( | |
a -> second.flatMap( | |
b -> fn.apply(a, b))); | |
} | |
public <C> Optional2<C, B> flatMapFirst(BiFunction<A, B, Optional<C>> fn) { | |
return Optional2.of(flatMap(fn), second); | |
} | |
public <C> Optional2<C, B> flatMapFirst(Function<A, Optional<C>> fn) { | |
return Optional2.of(first.flatMap(fn), second); | |
} | |
public <C> Optional2<A, C> flatMapSecond(BiFunction<A, B, Optional<C>> fn) { | |
return Optional2.of(first, flatMap(fn)); | |
} | |
public <C> Optional2<A, C> flatMapSecond(Function<B, Optional<C>> fn) { | |
return Optional2.of(first, second.flatMap(fn)); | |
} | |
public <C, D> Optional2<C, D> biFlatMap(BiFunction<A, B, Optional2<C, D>> fn) { | |
return first.flatMap( | |
a -> second.map( | |
b -> fn.apply(a, b) | |
)).orElseGet(Optional2::empty); | |
} | |
public Optional2<A, B> filter(BiPredicate<A, B> predicate) { | |
return map(predicate::test).orElse(false) ? this | |
: Optional2.of(Optional.empty(), Optional.empty()); | |
} | |
public Optional2<A, B> filterFirst(BiPredicate<A, B> predicate) { | |
return map(predicate::test).orElse(false) ? this | |
: Optional2.of(Optional.empty(), second); | |
} | |
public Optional2<A, B> filterFirst(Predicate<A> predicate) { | |
return Optional2.of(first.filter(predicate), second); | |
} | |
public Optional2<A, B> filterSecond(BiPredicate<A, B> predicate) { | |
return map(predicate::test).orElse(false) ? this | |
: Optional2.of(first, Optional.empty()); | |
} | |
public Optional2<A, B> filterSecond(Predicate<B> predicate) { | |
return Optional2.of(first, second.filter(predicate)); | |
} | |
public boolean bothPresent() { | |
return first.isPresent() && second.isPresent(); | |
} | |
public boolean bothEmpty() { | |
return !first.isPresent() && !second.isPresent(); | |
} | |
public Optional2<A, B> ifBothPresent(BiConsumer<A, B> consumer) { | |
first.ifPresent( | |
a -> second.ifPresent( | |
b -> consumer.accept(a, b))); | |
return this; | |
} | |
public Optional2<A, B> ifBothEmpty(Runnable runnable) { | |
if (!first.isPresent() && !second.isPresent()) { | |
runnable.run(); | |
} | |
return this; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
Optional2<?, ?> optional2 = (Optional2<?, ?>) o; | |
return first.equals(optional2.first) && | |
second.equals(optional2.second); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(first, second); | |
} | |
@Override | |
public String toString() { | |
return String.format("Optional2[%s,%s]", | |
first.map(Object::toString).orElse("<empty>"), | |
second.map(Object::toString).orElse("<empty>")); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment