diff --git a/README.org b/README.org index 918137b..1550087 100644 --- a/README.org +++ b/README.org @@ -25,8 +25,10 @@ - [Maybe] - [Result] - [Tree] + - [Lazy] - [Either] + ** Maven Usage #+BEGIN_SRC xml @@ -925,6 +927,51 @@ #+END_SRC +** Lazy + + A Lazy evaluated expression. Using a Supplier to provide the value, only + evaluates the value when required, and never more than once. + +*** Static Constructors + +**** =static Lazy of(Supplier supplier)= + + Create a new Lazy value from the supplier. + + #+BEGIN_SRC java + final Suppler supplier = ...; + final Lazy lazy = Lazy.of(supplier); + #+END_SRC + +*** Instance Methods + +**** =boolean isEvaluated()= + + Checks if the value has been evaluated. + + #+BEGIN_SRC java + final Lazy lazy = ...; + final boolean isEvaluated = lazy.isEvaluated(); + #+END_SRC + +**** =T value()= + + The value, evaluating it if necessary. + + #+BEGIN_SRC java + final Lazy lazy = ...; + final UUID value = lazy.value(); + #+END_SRC + +**** = Lazy map(Function f)= + + Maps the Lazy instance into a new Lazy instance using the function. + + #+BEGIN_SRC java + final Lazy uuidLazy = ...; + final Lazy stringLazy = uuidLazy.map(v -> v.toString()); + #+END_SRC + ** Either Allows handling a value that can be one of two types, a left value/type or a diff --git a/src/main/java/net/kemitix/mon/lazy/Lazy.java b/src/main/java/net/kemitix/mon/lazy/Lazy.java new file mode 100644 index 0000000..ebd593c --- /dev/null +++ b/src/main/java/net/kemitix/mon/lazy/Lazy.java @@ -0,0 +1,67 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 Paul Campbell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package net.kemitix.mon.lazy; + +import net.kemitix.mon.Functor; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Lazy evaluation of 'expensive' expressions. + * + * @param the type of the value + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public interface Lazy extends Functor> { + + /** + * Create a new Lazy value from the supplier. + * + * @param supplier the source of the value + * @param the type of the value + * @return a Lazy wrapper of the Supplier + */ + public static Lazy of(final Supplier supplier) { + return new LazySupplier<>(supplier); + } + + /** + * Checks if the value has been evaluated. + * + * @return true if the value has been evaluated. + */ + public abstract boolean isEvaluated(); + + /** + * The value, evaluating it if necessary. + * + *

Does not evaluate the value more than once.

+ * + * @return the evaluated value + */ + public abstract T value(); + + @Override + public abstract Lazy map(Function f); +} diff --git a/src/main/java/net/kemitix/mon/lazy/LazySupplier.java b/src/main/java/net/kemitix/mon/lazy/LazySupplier.java new file mode 100644 index 0000000..061c61b --- /dev/null +++ b/src/main/java/net/kemitix/mon/lazy/LazySupplier.java @@ -0,0 +1,75 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 Paul Campbell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package net.kemitix.mon.lazy; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Implementation of Lazy using a Supplier. + * + * @param the type of the value + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +class LazySupplier implements Lazy { + + private final Supplier supplier; + private final AtomicBoolean evaluated = new AtomicBoolean(false); + private final AtomicReference value = new AtomicReference<>(); + + /** + * Creates a new Lazy wrapper for the Supplier. + * + * @param supplier the source of the value + */ + LazySupplier(final Supplier supplier) { + this.supplier = supplier; + } + + @Override + public boolean isEvaluated() { + return evaluated.get(); + } + + @Override + public T value() { + if (evaluated.get()) { + return value.get(); + } + synchronized (value) { + if (!evaluated.get()) { + value.set(supplier.get()); + evaluated.set(true); + } + } + return value.get(); + } + + @Override + public Lazy map(final Function f) { + return Lazy.of(() -> f.apply(value())); + } + +} diff --git a/src/main/java/net/kemitix/mon/lazy/package-info.java b/src/main/java/net/kemitix/mon/lazy/package-info.java new file mode 100644 index 0000000..0782b4c --- /dev/null +++ b/src/main/java/net/kemitix/mon/lazy/package-info.java @@ -0,0 +1,28 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 Paul Campbell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Deferred evaluation. + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ + +package net.kemitix.mon.lazy; diff --git a/src/test/java/net/kemitix/mon/lazy/LazySupplierTest.java b/src/test/java/net/kemitix/mon/lazy/LazySupplierTest.java new file mode 100644 index 0000000..e2ccf5c --- /dev/null +++ b/src/test/java/net/kemitix/mon/lazy/LazySupplierTest.java @@ -0,0 +1,141 @@ +package net.kemitix.mon.lazy; + +import org.assertj.core.api.WithAssertions; +import org.junit.Test; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +public class LazySupplierTest implements WithAssertions { + + @Test + public void whenCreateLazyThenSupplierIsNotCalled() { + //given + final AtomicBoolean supplierCalled = new AtomicBoolean(false); + final Supplier supplier = () -> { + supplierCalled.set(true); + return UUID.randomUUID(); + }; + //when + final Lazy lazy = Lazy.of(supplier); + //then + assertThat(supplierCalled).isFalse(); + } + + @Test + public void whenCreateLazyThenIsEvaluatedIsFalse() { + //given + final Supplier supplier = UUID::randomUUID; + //when + final Lazy lazy = Lazy.of(supplier); + //then + assertThat(lazy.isEvaluated()).isFalse(); + } + + @Test + public void whenValueThenSupplierIsCalled() { + //given + final AtomicBoolean supplierCalled = new AtomicBoolean(false); + final Supplier supplier = () -> { + supplierCalled.set(true); + return UUID.randomUUID(); + }; + final Lazy lazy = Lazy.of(supplier); + //when + lazy.value(); + //then + assertThat(supplierCalled).isTrue(); + } + + @Test + public void whenValueThenValueIsSameAsSupplier() { + //given + final UUID uuid = UUID.randomUUID(); + final Supplier supplier = () -> uuid; + final Lazy lazy = Lazy.of(supplier); + //when + final UUID value = lazy.value(); + //then + assertThat(value).isSameAs(uuid); + } + + @Test + public void whenValueThenIsEvaluatedIsTrue() { + //given + final Supplier supplier = () -> UUID.randomUUID(); + final Lazy lazy = Lazy.of(supplier); + //when + lazy.value(); + //then + assertThat(lazy.isEvaluated()).isTrue(); + } + + @Test + public void whenValueCalledTwiceThenSupplierIsNotCalledAgain() { + //given + final AtomicInteger supplierCalledCounter = new AtomicInteger(0); + final Supplier supplier = () -> { + supplierCalledCounter.incrementAndGet(); + return UUID.randomUUID(); + }; + final Lazy lazy = Lazy.of(supplier); + //when + lazy.value(); + lazy.value(); + //then + assertThat(supplierCalledCounter).hasValue(1); + } + + @Test + public void whenMapLazyThenSupplierNotCalled() { + //given + final UUID uuid = UUID.randomUUID(); + final AtomicBoolean supplierCalled = new AtomicBoolean(false); + final Supplier supplier = () -> { + supplierCalled.set(true); + return uuid; + }; + final Lazy uuidLazy = Lazy.of(supplier); + //when + uuidLazy.map(UUID::toString); + //then + assertThat(supplierCalled).isFalse(); + } + + @Test + public void whenMapLazyValueThenSupplierIsCalled() { + //given + final UUID uuid = UUID.randomUUID(); + final AtomicBoolean supplierCalled = new AtomicBoolean(false); + final Supplier supplier = () -> { + supplierCalled.set(true); + return uuid; + }; + final Lazy uuidLazy = Lazy.of(supplier); + final Lazy stringLazy = uuidLazy.map(UUID::toString); + //when + stringLazy.value(); + //then + assertThat(supplierCalled).isTrue(); + } + + @Test + public void whenMapLazyValueThenValueIsCorrect() { + //given + final UUID uuid = UUID.randomUUID(); + final AtomicBoolean supplierCalled = new AtomicBoolean(false); + final Supplier supplier = () -> { + supplierCalled.set(true); + return uuid; + }; + final Lazy uuidLazy = Lazy.of(supplier); + final Lazy stringLazy = uuidLazy.map(UUID::toString); + //when + final String value = stringLazy.value(); + //then + assertThat(value).isEqualTo(uuid.toString()); + } + +} \ No newline at end of file