Skip to content

Instantly share code, notes, and snippets.

@mike-neck
Last active August 29, 2015 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mike-neck/ebb6e1b170f8db8250f8 to your computer and use it in GitHub Desktop.
Save mike-neck/ebb6e1b170f8db8250f8 to your computer and use it in GitHub Desktop.
無駄にLazyなOptional
/*
* Copyright 2015Shinya Mochida
*
* Licensed under the Apache License,Version2.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.
*/
import OptionalChain.Head;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public interface LazyOptional<T> {
static <O> LazyOptional<O> of(Supplier<O> source) {
return new Head<>(source);
}
// intermediate operations
/**
* Mapping to another object operation.
* <p>
* This is intermediate operation.
* </p>
* @param fun - mapping function
* @param <R> - result type
* @return - {@link LazyOptional} which might holds value.
*/
<R> LazyOptional<R> map (Function<? super T, ? extends R> fun);
/**
* Mapping to another {@link LazyOptional} object operation.
* <P>
* This is intermediate operation.
* </P>
* @param fun - mapping function which returns {@link LazyOptional}
* @param <R> - result type
* @return - {@link LazyOptional} which might holds value.
*/
<R> LazyOptional<R> flatMap (Function<? super T, ? extends LazyOptional<R>> fun);
/**
* Filtering value operation.
* <p>
* This is intermediate operation.
* </p>
* @param predicate - filtering function
* @return - If the value matches condition non-empty {@link LazyOptional}, if not empty {@link LazyOptional}
*/
LazyOptional<T> filter(Predicate<? super T> predicate);
// terminal operations
/**
* Value retrieving operation.
* <p>
* This is terminal operation.
* </p>
* @param defValue - default value of {@code T}
* @return - If this is empty, a given parameter will be returned. If not, the value.
*/
T orElse(T defValue);
/**
* Value retrieving operation.
* <p>
* This is terminal operation.
* </p>
* @param supplier - {@link Supplier} which produces default value of {@code T}.
* @return - If this is empty, a value produced by a given {@link Supplier} will be returned. If not, the value.
*/
T orElse(Supplier<? extends T> supplier);
/**
* Value consuming operation.
* <p>
* This is terminal operation.
* </p>
* @param action - a {@link Consumer} which consumes the value if present.
*/
void act(Consumer<? super T> action);
interface OnEmptyAction {
void act();
}
/**
* Value consuming operation with default action.
* @param action - A {@link Consumer} which consumes the value if present.
* @param defaultAction - An action which runs if this is empty.
*/
void act(Consumer<? super T> action, OnEmptyAction defaultAction);
static OnEmptyAction ifNotPresent(Runnable runnable) {
return runnable::run;
}
/**
* Terminal operation on empty value.
* @param action - An action which runs if this is empty.
*/
void actWhenEmpty(OnEmptyAction action);
}
/*
* Copyright 2015Shinya Mochida
*
* Licensed under the Apache License,Version2.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.
*/
import org.junit.Test;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class LazyOptionalTest {
@Test
public void onlyHeadWithValue() {
LazyOptional<Integer> lzop = LazyOptional.of(() -> 1);
assertThat(lzop.orElse(0), is(1));
}
@Test
public void onlyHeadWithoutValue() {
LazyOptional<Integer> lzop = LazyOptional.of(EMPTY_INT_SUPPLIER);
assertThat(lzop.orElse(0), is(0));
}
@Test
public void mappingStringToIntWithValue() {
LazyOptional<Integer> lzop = LazyOptional.of(() -> "foo")
.map(String::length);
assertThat(lzop.orElse(0), is(3));
}
@Test
public void mappingStringToIntWithoutValue() {
LazyOptional<Integer> lzop = LazyOptional.of(EMPTY_STRING_SUPPLIER)
.map(String::length);
assertThat(lzop.orElse(100), is(100));
}
@Test
public void cutOffAndMappingWithValue() {
LazyOptional<Integer> lzop = LazyOptional.of(() -> "foo")
.filter(lengthGreaterThan(3))
.map(String::length);
assertThat(lzop.orElse(100), is(100));
}
@Test
public void passFilteringAndMappingWithValue() {
LazyOptional<Integer> lzop = LazyOptional.of(() -> "foobar")
.filter(lengthGreaterThan(5))
.map(String::length);
assertThat(lzop.orElse(100), is(6));
}
@Test
public void flatMappingToEmptyWithValue() {
LazyOptional<Integer> lzop = LazyOptional.of(() -> new LazyOptionable("foo"))
.flatMap(LazyOptionable::lengthMinus3)
.map(i -> i + 1);
assertThat(lzop.orElse(100), is(100));
}
@Test
public void flatMappingToNonEmptyValueWithValue() {
LazyOptional<Integer> lzop = LazyOptional.of(() -> new LazyOptionable("foo1"))
.flatMap(LazyOptionable::lengthMinus3)
.map(i -> i + 1);
assertThat(lzop.orElse(100), is(5));
}
@Test
public void actOnNotEmpty() {
StringBuilder sb = new StringBuilder();
LazyOptional.of(() -> "foo")
.map(s -> s.replace('o', 'a'))
.act(sb::append);
assertThat(sb.toString(), is("faa"));
}
@Test
public void onEmptyAction() {
StringBuilder sb = new StringBuilder();
LazyOptional.of(EMPTY_STRING_SUPPLIER)
.filter(lengthGreaterThan(1))
.map(s -> s.replace('o', 'a'))
.act(sb::append, () -> sb.append("empty"));
assertThat(sb.toString(), is("empty"));
}
private static Predicate<String> lengthGreaterThan(int length) {
return s -> s.length() > length;
}
private static Supplier<Integer> EMPTY_INT_SUPPLIER = () -> null;
private static Supplier<String> EMPTY_STRING_SUPPLIER = () -> null;
private static class LazyOptionable {
private final String value;
private LazyOptionable(String value) {
this.value = value;
}
LazyOptional<Integer> lengthMinus3() {
return LazyOptional.of(value.length() <= 3 ? EMPTY_INT_SUPPLIER: value::length);
}
}
}
/*
* Copyright 2015Shinya Mochida
*
* Licensed under the Apache License,Version2.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.
*/
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
abstract class Operation<O, N> implements Consumer<O> {
protected final Consumer<? super N> nextOp;
protected Operation(Consumer<? super N> nextOp) {
this.nextOp = Objects.requireNonNull(nextOp);
}
static class EndOperation<O> implements Consumer<O>, Supplier<O> {
private boolean empty = true;
private O obj;
@Override
public void accept(O obj) {
this.empty = obj == null;
this.obj = obj;
}
@Override
public O get() {
return obj;
}
public boolean isEmpty() {
return empty;
}
}
}
/*
* Copyright 2015Shinya Mochida
*
* Licensed under the Apache License,Version2.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.
*/
import Operation.EndOperation;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* @param <IN> - a type of value before operation
* @param <OUT> - a type of value after operation
*/
abstract class OptionalChain<IN, OUT> implements LazyOptional<OUT> {
protected final OptionalChain<?, IN> previous;
protected OptionalChain<OUT, ?> next;
protected final Supplier<OUT> source;
protected final boolean original;
OptionalChain(Supplier<OUT> source) {
this.previous = null;
this.source = Objects.requireNonNull(source);
this.original = true;
}
OptionalChain(OptionalChain<?, IN> previous) {
this.previous = Objects.requireNonNull(previous);
this.source = null;
this.original = false;
previous.next = this;
}
// intermediate operations
@Override
public <R> LazyOptional<R> map(final Function<? super OUT, ? extends R> fun) {
Objects.requireNonNull(fun);
return new IntermediateChain<OUT, R>(this) {
@Override
Consumer<OUT> wrapOperation(Consumer<R> op) {
return new Operation<OUT, R>(op) {
@Override
public void accept(OUT out) {
if(out != null) {
nextOp.accept(fun.apply(out));
}
}
};
}
};
}
@Override
public <R> LazyOptional<R> flatMap(final Function<? super OUT, ? extends LazyOptional<R>> fun) {
Objects.requireNonNull(fun);
return new IntermediateChain<OUT, R>(this) {
@Override
Consumer<OUT> wrapOperation(Consumer<R> operation) {
return new Operation<OUT, R>(operation) {
@Override
public void accept(OUT out) {
if (out != null) {
fun.apply(out).act(nextOp);
}
}
};
}
};
}
@Override
public LazyOptional<OUT> filter(final Predicate<? super OUT> predicate) {
Objects.requireNonNull(predicate);
return new IntermediateChain<OUT, OUT>(this){
@Override
Consumer<OUT> wrapOperation(Consumer<OUT> operation) {
return new Operation<OUT, OUT>(operation) {
@Override
public void accept(OUT out) {
if (out != null && predicate.test(out)) {
nextOp.accept(out);
}
}
};
}
};
}
// terminal operations
@Override
public OUT orElse(OUT defValue) {
return orElse(() -> defValue);
}
@Override
public OUT orElse(Supplier<? extends OUT> supplier) {
EndOperation<OUT> end = evaluate();
OUT result;
if (((result = end.get()) == null) || end.isEmpty()) {
return supplier.get();
} else {
return result;
}
}
@Override
public void act(Consumer<? super OUT> action) {
act(action, EMPTY_ACTION);
}
@Override
public void act(Consumer<? super OUT> action, OnEmptyAction defaultAction) {
EndOperation<OUT> end = evaluate();
OUT value;
if ((value = end.get()) != null || !end.isEmpty()) {
Objects.requireNonNull(action).accept(value);
} else {
defaultAction.act();
}
}
@Override
public void actWhenEmpty(OnEmptyAction action) {
act(DO_NOTHING, action);
}
private static final OnEmptyAction EMPTY_ACTION = () -> {};
private static final Consumer<Object> DO_NOTHING = o -> {};
@SuppressWarnings("unchecked")
private EndOperation<OUT> evaluate() {
if (this.original) {
EndOperation<OUT> op = new EndOperation<>();
op.accept(source.get());
return op;
} else {
EndOperation<OUT> op = new EndOperation<>();
OptionalChain chain = this;
Consumer cn = op;
while (!chain.original) {
IntermediateChain ic = (IntermediateChain) chain;
cn = ic.wrapOperation(cn);
chain = chain.previous;
}
cn.accept(chain.source.get());
return op;
}
}
// implementations
static class Head<I, O> extends OptionalChain<I, O> {
Head(Supplier<O> source) {
super(source);
}
}
private abstract static class IntermediateChain<I, O> extends OptionalChain<I, O> {
IntermediateChain(OptionalChain<?, I> previous) {
super(previous);
}
abstract Consumer<I> wrapOperation(Consumer<O> operation);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment