Last active
August 29, 2015 14:22
-
-
Save mike-neck/ebb6e1b170f8db8250f8 to your computer and use it in GitHub Desktop.
無駄にLazyなOptional
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
/* | |
* 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); | |
} |
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
/* | |
* 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); | |
} | |
} | |
} |
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
/* | |
* 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; | |
} | |
} | |
} |
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
/* | |
* 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