From 7166ed016e9ccbfb8e9d53cf76eb467cfaa8b473 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 26 Nov 2017 19:55:28 +0000 Subject: [PATCH 01/16] version set to 0.4.0-SNAPSHOT --- CHANGELOG | 5 +++++ pom.xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 123b8df..9c9935a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ CHANGELOG ========= +0.4.0 +----- + +* + 0.3.0 ----- diff --git a/pom.xml b/pom.xml index 58bac15..12c8406 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 3.2.4 mon - 0.3.0 + 0.4.0-SNAPSHOT 4.12 From f49df2d895602595ee818822f0223eb2c315e1d6 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Dec 2017 17:23:14 +0000 Subject: [PATCH 02/16] Remove need in equals() for explicit type --- src/main/java/net/kemitix/mon/TypeAlias.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/kemitix/mon/TypeAlias.java b/src/main/java/net/kemitix/mon/TypeAlias.java index f5dc0c5..92ef4cb 100644 --- a/src/main/java/net/kemitix/mon/TypeAlias.java +++ b/src/main/java/net/kemitix/mon/TypeAlias.java @@ -75,11 +75,11 @@ public abstract class TypeAlias { @SuppressWarnings("unchecked") public final boolean equals(final Object o) { if (o instanceof TypeAlias) { - if (((TypeAlias) o).type.equals(type)) { - return ((TypeAlias) o).map(getValue()::equals); - } else { - return false; - } + final TypeAlias other = (TypeAlias) o; + final Object otherValue = other.getValue(); + final Class otherValueClass = otherValue.getClass(); + return otherValueClass.equals(getValue().getClass()) + && otherValue.equals(getValue()); } return map(o::equals); } From 2f16ab67147ff73d4b533d7daf7f631e50fe4a09 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Dec 2017 17:25:01 +0000 Subject: [PATCH 03/16] Remove explicit type as a constructor parameter This rolls back the change made for 0.3.0, reverting the API for TypeAlias --- src/main/java/net/kemitix/mon/TypeAlias.java | 9 +----- .../java/net/kemitix/mon/TypeAliasTest.java | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/kemitix/mon/TypeAlias.java b/src/main/java/net/kemitix/mon/TypeAlias.java index 92ef4cb..4e93272 100644 --- a/src/main/java/net/kemitix/mon/TypeAlias.java +++ b/src/main/java/net/kemitix/mon/TypeAlias.java @@ -38,20 +38,13 @@ public abstract class TypeAlias { */ private final T value; - private final Class type; - /** * Constructor. * * @param value the value - * @param type the type of the value */ - protected TypeAlias( - final T value, - final Class type - ) { + protected TypeAlias(final T value) { this.value = value; - this.type = type; } /** diff --git a/src/test/java/net/kemitix/mon/TypeAliasTest.java b/src/test/java/net/kemitix/mon/TypeAliasTest.java index 41c4222..c462d8b 100644 --- a/src/test/java/net/kemitix/mon/TypeAliasTest.java +++ b/src/test/java/net/kemitix/mon/TypeAliasTest.java @@ -10,7 +10,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class TypeAliasTest { @Test - public void shouldCreateATypeAliasAndGetTheValue() throws Exception { + public void shouldCreateATypeAliasAndGetTheValue() { //given final String value = "value"; //when @@ -19,25 +19,25 @@ public class TypeAliasTest { assertThat(typeAlias.map(value::equals)).isTrue(); } + private TypeAlias givenTypeAlias(final String value) { + return new TypeAlias(value) { + }; + } + @Test public void shouldCreateATypeAliasWithNestedGenericTypes() { //given final Iterable iterable = Collections.emptyList(); //when final TypeAlias> typeAlias = - new TypeAlias>(iterable, Iterable.class) { + new TypeAlias>(iterable) { }; //then assertThat(typeAlias.map(iterable::equals)).isTrue(); } - private TypeAlias givenTypeAlias(final String value) { - return new TypeAlias(value, String.class) { - }; - } - @Test - public void shouldCreateAnAliasedTypeAndGetTheValue() throws Exception { + public void shouldCreateAnAliasedTypeAndGetTheValue() { //given final String value = "value"; //when @@ -50,13 +50,14 @@ public class TypeAliasTest { public void shouldNotBeEqualWhenValueTypesAreDifferent() { //given final TypeAlias stringTypeAlias = givenTypeAlias("1"); - final TypeAlias integerTypeAlias = new TypeAlias(1, Integer.class){}; + final TypeAlias integerTypeAlias = new TypeAlias(1) { + }; //then assertThat(stringTypeAlias).isNotEqualTo(integerTypeAlias); } @Test - public void shouldBeEqualWhenValuesAreTheSame() throws Exception { + public void shouldBeEqualWhenValuesAreTheSame() { //given final String value = "value"; final AnAlias anAlias1 = AnAlias.of(value); @@ -66,7 +67,7 @@ public class TypeAliasTest { } @Test - public void shouldBeEqualToUnAliasedValue() throws Exception { + public void shouldBeEqualToUnAliasedValue() { //given final String value = "value"; final AnAlias anAlias = AnAlias.of(value); @@ -75,7 +76,7 @@ public class TypeAliasTest { } @Test - public void shouldHaveHashCodeOfValue() throws Exception { + public void shouldHaveHashCodeOfValue() { //given final String value = "value"; final AnAlias anAlias = AnAlias.of(value); @@ -84,7 +85,7 @@ public class TypeAliasTest { } @Test - public void shouldHaveSameToStringAsAliasedType() throws Exception { + public void shouldHaveSameToStringAsAliasedType() { //given final String value = "value"; //when @@ -112,7 +113,7 @@ public class TypeAliasTest { * @param value the value */ protected AnAlias(final String value) { - super(value, String.class); + super(value); } protected static AnAlias of(final String value) { From 3c2b7dd5a171abb1d4147afb565f64d4b1076926 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Dec 2017 17:26:29 +0000 Subject: [PATCH 04/16] Make TypeAlias.getValue() public again Another revert from 0.3.0. TypeAlias is not meant to be monadic in any form. It is a simple alias for another (non-generic) type. --- src/main/java/net/kemitix/mon/TypeAlias.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/mon/TypeAlias.java b/src/main/java/net/kemitix/mon/TypeAlias.java index 4e93272..050abbe 100644 --- a/src/main/java/net/kemitix/mon/TypeAlias.java +++ b/src/main/java/net/kemitix/mon/TypeAlias.java @@ -87,7 +87,7 @@ public abstract class TypeAlias { * * @return the value */ - private T getValue() { + public T getValue() { return value; } } From 27019b23c195c546cda1e93391b5660e4e88c33d Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Dec 2017 17:35:18 +0000 Subject: [PATCH 05/16] Update and clarify TypeAlias tests --- .../java/net/kemitix/mon/TypeAliasTest.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/test/java/net/kemitix/mon/TypeAliasTest.java b/src/test/java/net/kemitix/mon/TypeAliasTest.java index c462d8b..a077205 100644 --- a/src/test/java/net/kemitix/mon/TypeAliasTest.java +++ b/src/test/java/net/kemitix/mon/TypeAliasTest.java @@ -1,9 +1,11 @@ package net.kemitix.mon; +import org.assertj.core.util.Strings; import org.junit.Test; +import java.util.Arrays; import java.util.Collections; -import java.util.function.Function; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -16,7 +18,7 @@ public class TypeAliasTest { //when final TypeAlias typeAlias = givenTypeAlias(value); //then - assertThat(typeAlias.map(value::equals)).isTrue(); + assertThat(typeAlias.getValue()).isSameAs(value); } private TypeAlias givenTypeAlias(final String value) { @@ -33,17 +35,17 @@ public class TypeAliasTest { new TypeAlias>(iterable) { }; //then - assertThat(typeAlias.map(iterable::equals)).isTrue(); + assertThat(typeAlias.getValue()).isSameAs(iterable); } @Test - public void shouldCreateAnAliasedTypeAndGetTheValue() { + public void shouldCreateATypeAliasSubclassAndGetTheValue() { //given final String value = "value"; //when final AnAlias anAlias = AnAlias.of(value); //then - assertThat(anAlias.map(value::equals)).isTrue(); + assertThat(anAlias.getValue()).isSameAs(value); } @Test @@ -67,7 +69,7 @@ public class TypeAliasTest { } @Test - public void shouldBeEqualToUnAliasedValue() { + public void shouldBeEqualToRawValue() { //given final String value = "value"; final AnAlias anAlias = AnAlias.of(value); @@ -87,22 +89,22 @@ public class TypeAliasTest { @Test public void shouldHaveSameToStringAsAliasedType() { //given - final String value = "value"; + final List value = Arrays.asList(1, 2, 3); //when - final AnAlias anAlias = AnAlias.of(value); + final TypeAlias> anAlias = new TypeAlias>(value) { + }; //then - assertThat(anAlias.toString()).isEqualTo(value); + assertThat(anAlias.toString()).isEqualTo(value.toString()); } @Test public void shouldMapTypeAlias() { //given final AnAlias anAlias = AnAlias.of("text"); - final Function function = v -> v; //when - final String value = anAlias.map(function); + final String value = anAlias.map(Strings::quote); //then - assertThat(value).isEqualTo("text"); + assertThat(value).isEqualTo("'text'"); } private static class AnAlias extends TypeAlias { From ba4380fe9892c7c4963cff3dccbbfcdbc0bf2860 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Dec 2017 17:38:04 +0000 Subject: [PATCH 06/16] Clarify that toString(), equals() and hashCode() all delegate to value --- src/main/java/net/kemitix/mon/TypeAlias.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/net/kemitix/mon/TypeAlias.java b/src/main/java/net/kemitix/mon/TypeAlias.java index 050abbe..451d972 100644 --- a/src/main/java/net/kemitix/mon/TypeAlias.java +++ b/src/main/java/net/kemitix/mon/TypeAlias.java @@ -26,6 +26,9 @@ import java.util.function.Function; /** * Type Alias for other types. * + *

{@link #toString()}, {@link #equals(Object)} and {@link #hashCode()} are all transparent, returning the value + * for the type being aliased.

+ * * @param the type of the alias * * @author Paul Campbell (pcampbell@kemitix.net) From a215c9c137947de23b98f0cdfeaeadcdfdf56f0a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Dec 2017 17:38:35 +0000 Subject: [PATCH 07/16] Remove redundant unchecked warning --- src/main/java/net/kemitix/mon/TypeAlias.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/kemitix/mon/TypeAlias.java b/src/main/java/net/kemitix/mon/TypeAlias.java index 451d972..7676736 100644 --- a/src/main/java/net/kemitix/mon/TypeAlias.java +++ b/src/main/java/net/kemitix/mon/TypeAlias.java @@ -68,7 +68,6 @@ public abstract class TypeAlias { } @Override - @SuppressWarnings("unchecked") public final boolean equals(final Object o) { if (o instanceof TypeAlias) { final TypeAlias other = (TypeAlias) o; From db3596063e81c4f05f7781a214fa259428ef57d6 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Dec 2017 18:53:32 +0000 Subject: [PATCH 08/16] Update CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9c9935a..5b7d0cd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,7 +4,7 @@ CHANGELOG 0.4.0 ----- -* +* Restore public access for `TypeAlias.getValue()` 0.3.0 ----- From ec482211e0951f55198469a34284a5f3904fbd0a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 9 Dec 2017 19:23:23 +0000 Subject: [PATCH 09/16] Add missing test shouldNotBeEqualWhenValuesAreNotTheSame --- src/test/java/net/kemitix/mon/TypeAliasTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/net/kemitix/mon/TypeAliasTest.java b/src/test/java/net/kemitix/mon/TypeAliasTest.java index a077205..08a5448 100644 --- a/src/test/java/net/kemitix/mon/TypeAliasTest.java +++ b/src/test/java/net/kemitix/mon/TypeAliasTest.java @@ -68,6 +68,15 @@ public class TypeAliasTest { assertThat(anAlias1).isEqualTo(anAlias2); } + @Test + public void shouldNotBeEqualWhenValuesAreNotTheSame() { + //given + final AnAlias valueA = AnAlias.of("value a"); + final AnAlias valueB = AnAlias.of("value b"); + //then + assertThat(valueA).isNotEqualTo(valueB); + } + @Test public void shouldBeEqualToRawValue() { //given From 41f9227afd14f86d65a7a97950e712f09068422b Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 10 Dec 2017 10:40:31 +0000 Subject: [PATCH 10/16] Allow Functor.map() to return type-safe Functor subclasses --- src/main/java/net/kemitix/mon/Functor.java | 7 ++++--- src/main/java/net/kemitix/mon/Identity.java | 2 +- src/main/java/net/kemitix/mon/Mon.java | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/kemitix/mon/Functor.java b/src/main/java/net/kemitix/mon/Functor.java index 0111ca1..7dc4fa1 100644 --- a/src/main/java/net/kemitix/mon/Functor.java +++ b/src/main/java/net/kemitix/mon/Functor.java @@ -34,18 +34,19 @@ import java.util.function.Function; * * * @param the type of the Functor + * @param the type of the mapped Functor * * @author Tomasz Nurkiewicz (?@?.?) */ -public interface Functor { +public interface Functor> { /** * Applies the function to the value within the Functor, returning the result within a Functor. * * @param f the function to apply - * @param the type of the result of the function + * @param the type of the content of the mapped functor * * @return a Functor containing the result of the function {@code f} applied to the value */ - Functor map(Function f); + F map(Function f); } diff --git a/src/main/java/net/kemitix/mon/Identity.java b/src/main/java/net/kemitix/mon/Identity.java index 1300d7d..4b4df37 100644 --- a/src/main/java/net/kemitix/mon/Identity.java +++ b/src/main/java/net/kemitix/mon/Identity.java @@ -33,7 +33,7 @@ import java.util.function.Function; * @author Paul Campbell (pcampbell@kemitix.net) */ @RequiredArgsConstructor -class Identity implements Functor { +class Identity implements Functor> { private final T value; diff --git a/src/main/java/net/kemitix/mon/Mon.java b/src/main/java/net/kemitix/mon/Mon.java index 8abe82d..b591044 100644 --- a/src/main/java/net/kemitix/mon/Mon.java +++ b/src/main/java/net/kemitix/mon/Mon.java @@ -37,7 +37,7 @@ import java.util.function.Supplier; * @author Paul Campbell (pcampbell@kemitix.net) */ @RequiredArgsConstructor(access = AccessLevel.PROTECTED) -public class Mon implements Functor { +public class Mon implements Functor> { /** * The value. From e0eb0614c321fd8f030cc7560f5a1a94fa08e0ef Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 10 Dec 2017 16:11:12 +0000 Subject: [PATCH 11/16] Add `Maybe`, `Just`, `Nothing` --- CHANGELOG | 1 + README.md | 34 +++++ src/main/java/net/kemitix/mon/Just.java | 94 ++++++++++++ src/main/java/net/kemitix/mon/Maybe.java | 145 +++++++++++++++++++ src/main/java/net/kemitix/mon/Nothing.java | 75 ++++++++++ src/test/java/net/kemitix/mon/MaybeTest.java | 141 ++++++++++++++++++ 6 files changed, 490 insertions(+) create mode 100644 src/main/java/net/kemitix/mon/Just.java create mode 100644 src/main/java/net/kemitix/mon/Maybe.java create mode 100644 src/main/java/net/kemitix/mon/Nothing.java create mode 100644 src/test/java/net/kemitix/mon/MaybeTest.java diff --git a/CHANGELOG b/CHANGELOG index 5b7d0cd..bbde639 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ CHANGELOG ----- * Restore public access for `TypeAlias.getValue()` +* Add `Maybe`, `Just`, `Nothing` 0.3.0 ----- diff --git a/README.md b/README.md index dd342cd..c823d44 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,37 @@ void foo(final Goal goal) { System.out.println("The goal is " + goal.getValue()); } ``` + +### Maybe (Just & Nothing) + +```java +assertThat(Maybe.maybe(null)).isEqualTo(Maybe.nothing()); +assertThat(Maybe.maybe(1)).isEqualTo(Maybe.just(1)); +assertThat(Maybe.nothing() + .orElseGet(() -> 1)).isEqualTo(1); +assertThat(Maybe.just(1) + .orElseGet(() -> 2)).isEqualTo(1); +assertThat(Maybe.nothing() + .orElse(1)).isEqualTo(1); +assertThat(Maybe.just(1) + .orElse(2)).isEqualTo(1); +assertThat(Maybe.just(1) + .filter(v -> v > 2)).isEqualTo(Maybe.nothing()); +assertThat(Maybe.just(3) + .filter(v -> v > 2)).isEqualTo(Maybe.just(3)); +assertThat(Maybe.just(1) + .toOptional()).isEqualTo(Optional.of(1)); +assertThat(Maybe.nothing() + .toOptional()).isEqualTo(Optional.empty()); +assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(Maybe.just(1)); +assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing()); +final AtomicInteger reference = new AtomicInteger(0); +assertThat(Maybe.just(1).peek(reference::set)).isEqualTo(Maybe.just(1)); +assertThat(reference).hasValue(1); +assertThat(Maybe.nothing().peek(v -> reference.incrementAndGet())).isEqualTo(Maybe.nothing()); +assertThat(reference).hasValue(1); +assertThatCode(() -> Maybe.just(1).orElseThrow(IllegalStateException::new)) + .doesNotThrowAnyException(); +assertThatThrownBy(() -> Maybe.nothing().orElseThrow(IllegalStateException::new)) + .isInstanceOf(IllegalStateException.class); +``` diff --git a/src/main/java/net/kemitix/mon/Just.java b/src/main/java/net/kemitix/mon/Just.java new file mode 100644 index 0000000..9437bc7 --- /dev/null +++ b/src/main/java/net/kemitix/mon/Just.java @@ -0,0 +1,94 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 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; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * A Maybe where a value is present. + * + * @param the type of the content + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public final class Just implements Maybe { + + private final T value; + + @Override + public Maybe map(final Function f) { + return new Just<>(f.apply(value)); + } + + @Override + public boolean equals(final Object other) { + return other instanceof Just && Objects.equals(this.value, ((Just) other).value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + @Override + public T orElseGet(final Supplier supplier) { + return value; + } + + @Override + public T orElse(final T otherValue) { + return this.value; + } + + @Override + public Maybe filter(final Predicate predicate) { + if (predicate.test(value)) { + return this; + } + return Maybe.nothing(); + } + + @Override + public Optional toOptional() { + return Optional.of(value); + } + + @Override + public Maybe peek(final Consumer consumer) { + consumer.accept(value); + return this; + } + + @Override + public void orElseThrow(final Supplier e) { + // do not throw + } +} diff --git a/src/main/java/net/kemitix/mon/Maybe.java b/src/main/java/net/kemitix/mon/Maybe.java new file mode 100644 index 0000000..3211d0c --- /dev/null +++ b/src/main/java/net/kemitix/mon/Maybe.java @@ -0,0 +1,145 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 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; + +import lombok.NonNull; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * A value that may or may not be present. + * + * @param the type of the content of the Just + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public interface Maybe extends Functor> { + + /** + * Create a Maybe for the value that is present. + * + * @param value the value, not null + * @param the type of the value + * + * @return a Maybe of the value + */ + static Maybe just(@NonNull final T value) { + return new Just<>(value); + } + + /** + * Create a Maybe for a lack of a value. + * + * @param the type of the missing value + * + * @return an empty Maybe + */ + @SuppressWarnings("unchecked") + static Maybe nothing() { + return (Maybe) Nothing.INSTANCE; + } + + /** + * Create a Maybe for the value that may or may not be present. + * + * @param value the value, may be null + * @param the type of the value + * + * @return a Maybe, either a Just, or Nothing if value is null + */ + static Maybe maybe(final T value) { + if (value == null) { + return nothing(); + } + return just(value); + } + + /** + * Create a Maybe from an {@link Optional}. + * + * @param optional the Optional + * @param the type of the Optional + * + * @return a Maybe + */ + static Maybe fromOptional(final Optional optional) { + return optional.map(Maybe::maybe) + .orElse(nothing()); + } + + /** + * Provide a value to use when Maybe is Nothing. + * + * @param supplier supplier for an alternate value + * + * @return a Maybe + */ + T orElseGet(Supplier supplier); + + /** + * A value to use when Maybe is Nothing. + * + * @param otherValue an alternate value + * + * @return a Maybe + */ + T orElse(T otherValue); + + /** + * Filter a Maybe by the predicate, replacing with Nothing when it fails. + * + * @param predicate the test + * + * @return the Maybe, or Nothing if the test returns false + */ + Maybe filter(Predicate predicate); + + /** + * Convert the Maybe to an {@link Optional}. + * + * @return an Optional containing a value for a Just, or empty for a Nothing + */ + Optional toOptional(); + + /** + * Provide the value within the Maybe, if it exists, to the Supplier, and returns the Maybe. + * + * @param consumer the Consumer to the value if present + * + * @return the Maybe + */ + Maybe peek(Consumer consumer); + + /** + * Throw the exception if the Maybe is a Nothing. + * + * @param e the exception to throw + * + * @throws Exception if the Maybe is a Nothing + */ + @SuppressWarnings("illegalthrows") + void orElseThrow(Supplier e) throws Exception; + +} diff --git a/src/main/java/net/kemitix/mon/Nothing.java b/src/main/java/net/kemitix/mon/Nothing.java new file mode 100644 index 0000000..f484b1f --- /dev/null +++ b/src/main/java/net/kemitix/mon/Nothing.java @@ -0,0 +1,75 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 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; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * A Maybe where no value is present. + * + * @param the type of the missing content + * + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public final class Nothing implements Maybe { + + protected static final Maybe INSTANCE = new Nothing<>(); + + @Override + public Maybe map(final Function f) { + return this; + } + + @Override + public T orElseGet(final Supplier supplier) { + return supplier.get(); + } + + @Override + public T orElse(final T otherValue) { + return otherValue; + } + + @Override + public Maybe filter(final Predicate predicate) { + return this; + } + + @Override + public Optional toOptional() { + return Optional.empty(); + } + + @Override + public Maybe peek(final Consumer consumer) { + return this; + } + + @Override + public void orElseThrow(final Supplier e) throws Exception { + throw e.get(); + } +} diff --git a/src/test/java/net/kemitix/mon/MaybeTest.java b/src/test/java/net/kemitix/mon/MaybeTest.java new file mode 100644 index 0000000..f0a241a --- /dev/null +++ b/src/test/java/net/kemitix/mon/MaybeTest.java @@ -0,0 +1,141 @@ +package net.kemitix.mon; + +import org.junit.Test; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +import static net.kemitix.mon.Maybe.just; +import static net.kemitix.mon.Maybe.maybe; +import static net.kemitix.mon.Maybe.nothing; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatNullPointerException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class MaybeTest { + + private static Predicate eq(final T value) { + return v -> Objects.equals(value, v); + } + + @Test + public void documentation() { + assertThat(Maybe.maybe(null)).isEqualTo(Maybe.nothing()); + assertThat(Maybe.maybe(1)).isEqualTo(Maybe.just(1)); + assertThat(Maybe.nothing() + .orElseGet(() -> 1)).isEqualTo(1); + assertThat(Maybe.just(1) + .orElseGet(() -> 2)).isEqualTo(1); + assertThat(Maybe.nothing() + .orElse(1)).isEqualTo(1); + assertThat(Maybe.just(1) + .orElse(2)).isEqualTo(1); + assertThat(Maybe.just(1) + .filter(v -> v > 2)).isEqualTo(Maybe.nothing()); + assertThat(Maybe.just(3) + .filter(v -> v > 2)).isEqualTo(Maybe.just(3)); + assertThat(Maybe.just(1) + .toOptional()).isEqualTo(Optional.of(1)); + assertThat(Maybe.nothing() + .toOptional()).isEqualTo(Optional.empty()); + assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(Maybe.just(1)); + assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing()); + final AtomicInteger reference = new AtomicInteger(0); + assertThat(Maybe.just(1) + .peek(reference::set)).isEqualTo(Maybe.just(1)); + assertThat(reference).hasValue(1); + assertThat(Maybe.nothing() + .peek(v -> reference.incrementAndGet())).isEqualTo(Maybe.nothing()); + assertThat(reference).hasValue(1); + assertThatCode(() -> Maybe.just(1) + .orElseThrow(IllegalStateException::new)).doesNotThrowAnyException(); + assertThatThrownBy(() -> Maybe.nothing() + .orElseThrow(IllegalStateException::new)).isInstanceOf( + IllegalStateException.class); + } + + @Test + public void justMustBeNonNull() { + assertThatNullPointerException().isThrownBy(() -> just(null)) + .withMessage("value"); + } + + @Test + public void nothingReusesTheSameInstance() { + assertThat(nothing()).isSameAs(nothing()); + } + + @Test + public void maybeAllowsNull() { + assertThat(just(1)).isEqualTo(maybe(1)); + assertThat(nothing()).isEqualTo(maybe(null)); + } + + @Test + public void map() { + assertThat(just(1).map(v -> v + 1)).isEqualTo(just(2)); + assertThat(nothing().map(v -> v)).isEqualTo(nothing()); + } + + @Test + public void testHashCode() { + assertThat(just(1).hashCode()).isEqualTo(Objects.hashCode(1)); + } + + @Test + public void orElseGet() { + assertThat(just(1).orElseGet(() -> -1)).isEqualTo(1); + assertThat(nothing().orElseGet(() -> -1)).isEqualTo(-1); + } + + @Test + public void orElse() { + assertThat(just(1).orElse(-1)).isEqualTo(1); + assertThat(nothing().orElse(-1)).isEqualTo(-1); + } + + @Test + public void filter() { + assertThat(just(1).filter(eq(1))).isEqualTo(just(1)); + assertThat(just(1).filter(eq(0))).isEqualTo(nothing()); + assertThat(nothing().filter(eq(1))).isEqualTo(nothing()); + } + + @Test + public void toOptional() { + assertThat(just(1).toOptional()).isEqualTo(Optional.of(1)); + assertThat(Maybe.nothing() + .toOptional()).isEqualTo(Optional.empty()); + } + + @Test + public void fromOptional() { + assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(just(1)); + assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing()); + } + + + @Test + public void peek() { + final AtomicInteger ref = new AtomicInteger(0); + assertThat(just(1).peek(x -> ref.incrementAndGet())).isEqualTo(just(1)); + assertThat(ref.get()).isEqualTo(1); + + assertThat(nothing().peek(x -> ref.incrementAndGet())).isEqualTo(nothing()); + assertThat(ref.get()).isEqualTo(1); + } + + @Test + public void justOrThrow() { + assertThatCode(() -> just(1).orElseThrow(IllegalStateException::new)).doesNotThrowAnyException(); + } + + @Test + public void nothingOrThrow() { + assertThatThrownBy(() -> nothing().orElseThrow(IllegalStateException::new)).isInstanceOf( + IllegalStateException.class); + } +} From e711184628a0da2ffe08ebe2e402c8b058e9bf78 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 10 Dec 2017 16:24:56 +0000 Subject: [PATCH 12/16] Remove copy of documentation tests --- src/test/java/net/kemitix/mon/MaybeTest.java | 36 -------------------- 1 file changed, 36 deletions(-) diff --git a/src/test/java/net/kemitix/mon/MaybeTest.java b/src/test/java/net/kemitix/mon/MaybeTest.java index f0a241a..bb8676d 100644 --- a/src/test/java/net/kemitix/mon/MaybeTest.java +++ b/src/test/java/net/kemitix/mon/MaybeTest.java @@ -21,42 +21,6 @@ public class MaybeTest { return v -> Objects.equals(value, v); } - @Test - public void documentation() { - assertThat(Maybe.maybe(null)).isEqualTo(Maybe.nothing()); - assertThat(Maybe.maybe(1)).isEqualTo(Maybe.just(1)); - assertThat(Maybe.nothing() - .orElseGet(() -> 1)).isEqualTo(1); - assertThat(Maybe.just(1) - .orElseGet(() -> 2)).isEqualTo(1); - assertThat(Maybe.nothing() - .orElse(1)).isEqualTo(1); - assertThat(Maybe.just(1) - .orElse(2)).isEqualTo(1); - assertThat(Maybe.just(1) - .filter(v -> v > 2)).isEqualTo(Maybe.nothing()); - assertThat(Maybe.just(3) - .filter(v -> v > 2)).isEqualTo(Maybe.just(3)); - assertThat(Maybe.just(1) - .toOptional()).isEqualTo(Optional.of(1)); - assertThat(Maybe.nothing() - .toOptional()).isEqualTo(Optional.empty()); - assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(Maybe.just(1)); - assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing()); - final AtomicInteger reference = new AtomicInteger(0); - assertThat(Maybe.just(1) - .peek(reference::set)).isEqualTo(Maybe.just(1)); - assertThat(reference).hasValue(1); - assertThat(Maybe.nothing() - .peek(v -> reference.incrementAndGet())).isEqualTo(Maybe.nothing()); - assertThat(reference).hasValue(1); - assertThatCode(() -> Maybe.just(1) - .orElseThrow(IllegalStateException::new)).doesNotThrowAnyException(); - assertThatThrownBy(() -> Maybe.nothing() - .orElseThrow(IllegalStateException::new)).isInstanceOf( - IllegalStateException.class); - } - @Test public void justMustBeNonNull() { assertThatNullPointerException().isThrownBy(() -> just(null)) From dc89a0962f2706dd75c0aa77b18b050fc1a25e74 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 10 Dec 2017 16:33:49 +0000 Subject: [PATCH 13/16] Use static import of nothing() --- src/test/java/net/kemitix/mon/MaybeTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/kemitix/mon/MaybeTest.java b/src/test/java/net/kemitix/mon/MaybeTest.java index bb8676d..ecd0190 100644 --- a/src/test/java/net/kemitix/mon/MaybeTest.java +++ b/src/test/java/net/kemitix/mon/MaybeTest.java @@ -71,14 +71,14 @@ public class MaybeTest { @Test public void toOptional() { assertThat(just(1).toOptional()).isEqualTo(Optional.of(1)); - assertThat(Maybe.nothing() + assertThat(nothing() .toOptional()).isEqualTo(Optional.empty()); } @Test public void fromOptional() { assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(just(1)); - assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing()); + assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(nothing()); } From d082215b12d195f8ede58397a424bf95f005b0b4 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 10 Dec 2017 16:53:41 +0000 Subject: [PATCH 14/16] Add tests for equality --- src/test/java/net/kemitix/mon/MaybeTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/net/kemitix/mon/MaybeTest.java b/src/test/java/net/kemitix/mon/MaybeTest.java index ecd0190..ba68b3c 100644 --- a/src/test/java/net/kemitix/mon/MaybeTest.java +++ b/src/test/java/net/kemitix/mon/MaybeTest.java @@ -32,6 +32,15 @@ public class MaybeTest { assertThat(nothing()).isSameAs(nothing()); } + @Test + public void equality() { + assertThat(just(1)).isEqualTo(just(1)); + assertThat(just(1)).isNotEqualTo(just(2)); + assertThat(just(1)).isNotEqualTo(nothing()); + assertThat(nothing()).isEqualTo(nothing()); + assertThat(just(1).equals("1")).isFalse(); + } + @Test public void maybeAllowsNull() { assertThat(just(1)).isEqualTo(maybe(1)); From 834612d85679c150dc2ef58f058c52b77eccc289 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 10 Dec 2017 17:02:11 +0000 Subject: [PATCH 15/16] version set to 0.4.0 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c823d44..1a03777 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ TypeAlias for Java net.kemitix mon - 0.3.0 + 0.4.0 ``` diff --git a/pom.xml b/pom.xml index 12c8406..ccf634f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 3.2.4 mon - 0.4.0-SNAPSHOT + 0.4.0 4.12 From c750b9480141661b516bb31a5b9f43552926f053 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 10 Dec 2017 17:16:03 +0000 Subject: [PATCH 16/16] Remove incomplete Identity Test --- src/test/java/net/kemitix/mon/IdentityTest.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/test/java/net/kemitix/mon/IdentityTest.java b/src/test/java/net/kemitix/mon/IdentityTest.java index 513a979..db95452 100644 --- a/src/test/java/net/kemitix/mon/IdentityTest.java +++ b/src/test/java/net/kemitix/mon/IdentityTest.java @@ -10,15 +10,6 @@ import org.junit.Test; */ public class IdentityTest implements WithAssertions { - @Test - public void functorLawMapIdEqualsId() { - //given - final String id = "id"; - //when - - //then - } - @Test public void canMapIdentityFromStringToInteger() { //given