diff --git a/patterns-modules/design-patterns-behavioral-2/pom.xml b/patterns-modules/design-patterns-behavioral-2/pom.xml index e484e118eb..c62e5ce205 100644 --- a/patterns-modules/design-patterns-behavioral-2/pom.xml +++ b/patterns-modules/design-patterns-behavioral-2/pom.xml @@ -14,4 +14,20 @@ 1.0.0-SNAPSHOT + + + org.apache.commons + commons-lang3 + ${apache.commons.version} + + + com.google.guava + guava + ${guava.version} + + + + + 3.14.0 + \ No newline at end of file diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Address.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Address.java new file mode 100644 index 0000000000..738be932bc --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Address.java @@ -0,0 +1,26 @@ +package com.baeldung.nullconversion; + +public class Address implements Cloneable { + + private ZipCode zipCode; + + public ZipCode getZipCode() { + return zipCode; + } + + public void setZipCode(ZipCode zipCode) { + this.zipCode = zipCode; + } + + @Override + protected Address clone() { + Address address; + try { + address = ((Address) super.clone()); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + zipCode = zipCode.clone(); + return address; + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Delivery.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Delivery.java new file mode 100644 index 0000000000..3c584d9566 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Delivery.java @@ -0,0 +1,35 @@ +package com.baeldung.nullconversion; + +import java.util.Objects; + +public class Delivery { + + private String message; + + public Delivery(String message) { + this.message = message; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Delivery delivery = (Delivery) o; + + return Objects.equals(message, delivery.message); + } + + @Override + public int hashCode() { + return message != null ? message.hashCode() : 0; + } + + public static Delivery freeDelivery() { + return new Delivery("Free delivery"); + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/DeliveryService.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/DeliveryService.java new file mode 100644 index 0000000000..1d3b473cdc --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/DeliveryService.java @@ -0,0 +1,6 @@ +package com.baeldung.nullconversion; + +public interface DeliveryService { + + Delivery calculateDeliveryForPerson(Long id); +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Person.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Person.java new file mode 100644 index 0000000000..842d04575c --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/Person.java @@ -0,0 +1,26 @@ +package com.baeldung.nullconversion; + +public class Person implements Cloneable { + + private Address address; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @Override + protected Person clone() { + Person person; + try { + person = (Person) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + address = address.clone(); + return person; + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/ZipCode.java b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/ZipCode.java new file mode 100644 index 0000000000..0a4a1b2ed5 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/main/java/com/baeldung/nullconversion/ZipCode.java @@ -0,0 +1,27 @@ +package com.baeldung.nullconversion; + +public class ZipCode implements Cloneable { + + private String code; + + public ZipCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + @Override + protected ZipCode clone() { + try { + return (ZipCode) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/NullConversionUnitTest.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/NullConversionUnitTest.java new file mode 100644 index 0000000000..abe10f226c --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/NullConversionUnitTest.java @@ -0,0 +1,130 @@ +package com.baeldung.nullconversion; + +import static java.util.Objects.requireNonNullElse; +import static java.util.Objects.requireNonNullElseGet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.google.common.base.MoreObjects; +import java.util.Optional; +import java.util.function.Supplier; +import org.apache.commons.lang3.ObjectUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; + +class NullConversionUnitTest { + + @ParameterizedTest + @ArgumentsSource(ObjectsProvider.class) + void givenIfWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) { + String actual = defaultValue; + if (givenValue != null) { + actual = givenValue; + } + assertDefaultConversion(givenValue, defaultValue, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsSupplierProvider.class) + void givenIfWhenNotNullThenReturnsDefault(String givenValue, String expected, + Supplier expensiveSupplier) { + String actual; + if (givenValue != null) { + actual = givenValue; + } else { + actual = expensiveSupplier.get(); + } + assertDefaultConversion(givenValue, expected, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsProvider.class) + void givenTernaryWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) { + String actual = givenValue != null ? givenValue : defaultValue; + assertDefaultConversion(givenValue, defaultValue, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsSupplierProvider.class) + void givenLazyTernaryWhenNotNullThenReturnsDefault(String givenValue, String expected, + Supplier expensiveSupplier) { + String actual = givenValue != null ? givenValue : expensiveSupplier.get(); + assertDefaultConversion(givenValue, expected, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsProvider.class) + void givenObjectsWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) { + String actual = requireNonNullElse(givenValue, defaultValue); + assertDefaultConversion(givenValue, defaultValue, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsSupplierProvider.class) + void givenLazyObjectsWhenNotNullThenReturnsDefault(String givenValue, String expected, + Supplier expensiveSupplier) { + String actual = requireNonNullElseGet(givenValue, expensiveSupplier); + assertDefaultConversion(givenValue, expected, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsProvider.class) + void givenOptionalWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) { + String actual = Optional.ofNullable(givenValue).orElse(defaultValue); + assertDefaultConversion(givenValue, defaultValue, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsSupplierProvider.class) + void givenLazyOptionalWhenNotNullThenReturnsDefault(String givenValue, String expected, + Supplier expensiveSupplier) { + String actual = Optional.ofNullable(givenValue).orElseGet(expensiveSupplier); + assertDefaultConversion(givenValue, expected, actual); + } + + + @ParameterizedTest + @ArgumentsSource(ObjectsProvider.class) + void givenGuavaWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) { + String actual = MoreObjects.firstNonNull(givenValue, defaultValue); + assertDefaultConversion(givenValue, defaultValue, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsProvider.class) + void givenGuavaOptionalWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) { + String actual = com.google.common.base.Optional.fromNullable(givenValue).or(defaultValue); + assertDefaultConversion(givenValue, defaultValue, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsProvider.class) + void givenApacheWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) { + String actual = ObjectUtils.firstNonNull(givenValue, defaultValue); + assertDefaultConversion(givenValue, defaultValue, actual); + } + + @ParameterizedTest + @ArgumentsSource(ObjectsSupplierProvider.class) + void givenLazyApacheWhenNotNullThenReturnsDefault(String givenValue, String expected, + Supplier expensiveSupplier) { + String actual = ObjectUtils.getFirstNonNull(() -> givenValue, expensiveSupplier); + assertDefaultConversion(givenValue, expected, actual); + } + + @Test + void givenAllNullsWithObjectsWhenNotNullThenThrowException() { + assertThatExceptionOfType(NullPointerException.class) + .isThrownBy(() -> requireNonNullElse(null, null)); + } + + private static void assertDefaultConversion(String given, String expected, String actual) { + if (given == null) { + assertThat(actual).isEqualTo(expected); + } else { + assertThat(actual).isEqualTo(given); + } + } + +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/NullReturningPersonChainProvider.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/NullReturningPersonChainProvider.java new file mode 100644 index 0000000000..98232c2387 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/NullReturningPersonChainProvider.java @@ -0,0 +1,26 @@ +package com.baeldung.nullconversion; + +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +public class NullReturningPersonChainProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + Person person = new Person(); + Address address = new Address(); + ZipCode zipCode = new ZipCode("98-323"); + address.setZipCode(zipCode); + person.setAddress(address); + + return Stream.of( + Arguments.of(PersonMutatorUtil.cloneAndMutate(person, p -> p.getAddress().getZipCode().setCode(""))), + Arguments.of(PersonMutatorUtil.cloneAndMutate(person, p -> p.getAddress().setZipCode(null))), + Arguments.of(PersonMutatorUtil.cloneAndMutate(person, p -> p.setAddress(null))) + ); + + } + +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/ObjectsProvider.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/ObjectsProvider.java new file mode 100644 index 0000000000..213947a9ce --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/ObjectsProvider.java @@ -0,0 +1,18 @@ +package com.baeldung.nullconversion; + +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +public class ObjectsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(null, "default"), + Arguments.of("not null", "default") + ); + + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/ObjectsSupplierProvider.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/ObjectsSupplierProvider.java new file mode 100644 index 0000000000..2f7688b562 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/ObjectsSupplierProvider.java @@ -0,0 +1,20 @@ +package com.baeldung.nullconversion; + +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +public class ObjectsSupplierProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + Supplier supplier = () -> "default"; + return Stream.of( + Arguments.of(null, "default", supplier), + Arguments.of("not null", "default", supplier) + ); + + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/OnePersonDeliveryServiceUnitTest.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/OnePersonDeliveryServiceUnitTest.java new file mode 100644 index 0000000000..01cd658bd1 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/OnePersonDeliveryServiceUnitTest.java @@ -0,0 +1,39 @@ +package com.baeldung.nullconversion; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.baeldung.nullconversion.service.OnePersonExplicitDeliveryService; +import com.baeldung.nullconversion.service.OnePersonGuavaOptionalDeliveryService; +import com.baeldung.nullconversion.service.OnePersonOptionalDeliveryService; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; + +class OnePersonDeliveryServiceUnitTest { + + @ParameterizedTest + @ArgumentsSource(PersonProvider.class) + void givenMockDeliverServiceWhenNullValuesThenExplicitServiceHandleThem(Person person, Delivery expected) { + DeliveryService deliveryService = new OnePersonExplicitDeliveryService(person); + Delivery actual = deliveryService.calculateDeliveryForPerson(1L); + assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @ArgumentsSource(PersonProvider.class) + void givenMockDeliverServiceWhenNullValuesThenOptionalServiceHandleThem(Person person, Delivery expected) { + DeliveryService deliveryService = new OnePersonOptionalDeliveryService(person); + Delivery actual = deliveryService.calculateDeliveryForPerson(1L); + assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @ArgumentsSource(NullReturningPersonChainProvider.class) + void givenMockDeliverServiceWhenNullValuesThenGuavaOptionalServiceThrowsException(Person person) { + DeliveryService deliveryService = new OnePersonGuavaOptionalDeliveryService(person); + assertThatExceptionOfType(NullPointerException.class) + .isThrownBy(() -> deliveryService.calculateDeliveryForPerson(1L)); + + } +} \ No newline at end of file diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/PersonMutatorUtil.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/PersonMutatorUtil.java new file mode 100644 index 0000000000..3165ecad4e --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/PersonMutatorUtil.java @@ -0,0 +1,12 @@ +package com.baeldung.nullconversion; + +import java.util.function.Consumer; + +public class PersonMutatorUtil { + + public static Person cloneAndMutate(Person person, Consumer mutator) { + Person clone = person.clone(); + mutator.accept(clone); + return clone; + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/PersonProvider.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/PersonProvider.java new file mode 100644 index 0000000000..d31d9d8ec4 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/PersonProvider.java @@ -0,0 +1,34 @@ +package com.baeldung.nullconversion; + +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +public class PersonProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + Person person = new Person(); + Address address = new Address(); + ZipCode zipCode = new ZipCode("98-323"); + address.setZipCode(zipCode); + person.setAddress(address); + + return Stream.of( + Arguments.of(person, Delivery.freeDelivery()), + Arguments.of(cloneAndMutate(person, p -> p.getAddress().getZipCode().setCode("")), null), + Arguments.of(cloneAndMutate(person, p -> p.getAddress().setZipCode(null)), null), + Arguments.of(cloneAndMutate(person, p -> p.setAddress(null)), null), + Arguments.of(null, null) + ); + + } + + private static Person cloneAndMutate(Person person, Consumer mutator) { + Person clone = person.clone(); + mutator.accept(clone); + return clone; + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/MockOnePersonDeliveryServiceBase.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/MockOnePersonDeliveryServiceBase.java new file mode 100644 index 0000000000..973f8bce97 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/MockOnePersonDeliveryServiceBase.java @@ -0,0 +1,50 @@ +package com.baeldung.nullconversion.service; + +import com.baeldung.nullconversion.Address; +import com.baeldung.nullconversion.Delivery; +import com.baeldung.nullconversion.DeliveryService; +import com.baeldung.nullconversion.Person; +import com.baeldung.nullconversion.ZipCode; +import java.util.Optional; + +public abstract class MockOnePersonDeliveryServiceBase implements DeliveryService { + + private final Person person; + + public MockOnePersonDeliveryServiceBase(Person person) { + this.person = person; + } + + @Override + public Delivery calculateDeliveryForPerson(Long id) { + Person person = getPersonById(id); + if (person != null && person.getAddress() != null && person.getAddress().getZipCode() != null) { + ZipCode zipCode = person.getAddress().getZipCode(); + String code = zipCode.getCode(); + return calculateDeliveryForZipCode(code); + } + return null; + } + + public Delivery calculateDeliveryForPersonWithOptional(Long id) { + return Optional.ofNullable(getPersonById(id)) + .map(Person::getAddress) + .map(Address::getZipCode) + .map(ZipCode::getCode) + .map(this::calculateDeliveryForZipCode) + .orElse(null); + } + + protected Person getPersonById(Long id) { + return person; + } + + protected Delivery calculateDeliveryForZipCode(String zipCode) { + if (zipCode == null || zipCode.isEmpty()) { + return null; + } else { + return Delivery.freeDelivery(); + } + } + +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonExplicitDeliveryService.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonExplicitDeliveryService.java new file mode 100644 index 0000000000..652d0a002b --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonExplicitDeliveryService.java @@ -0,0 +1,24 @@ +package com.baeldung.nullconversion.service; + +import com.baeldung.nullconversion.Delivery; +import com.baeldung.nullconversion.Person; +import com.baeldung.nullconversion.ZipCode; + +public class OnePersonExplicitDeliveryService extends MockOnePersonDeliveryServiceBase { + + + public OnePersonExplicitDeliveryService(Person person) { + super(person); + } + + @Override + public Delivery calculateDeliveryForPerson(Long id) { + Person person = getPersonById(id); + if (person != null && person.getAddress() != null && person.getAddress().getZipCode() != null) { + ZipCode zipCode = person.getAddress().getZipCode(); + String code = zipCode.getCode(); + return calculateDeliveryForZipCode(code); + } + return null; + } +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonGuavaOptionalDeliveryService.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonGuavaOptionalDeliveryService.java new file mode 100644 index 0000000000..29d42789a0 --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonGuavaOptionalDeliveryService.java @@ -0,0 +1,26 @@ +package com.baeldung.nullconversion.service; + +import com.baeldung.nullconversion.Address; +import com.baeldung.nullconversion.Delivery; +import com.baeldung.nullconversion.Person; +import com.baeldung.nullconversion.ZipCode; +import com.google.common.base.Optional; + +public class OnePersonGuavaOptionalDeliveryService extends MockOnePersonDeliveryServiceBase { + + + public OnePersonGuavaOptionalDeliveryService(Person person) { + super(person); + } + + @Override + public Delivery calculateDeliveryForPerson(Long id) { + return Optional.fromNullable(getPersonById(id)) + .transform(Person::getAddress) + .transform(Address::getZipCode) + .transform(ZipCode::getCode) + .transform(this::calculateDeliveryForZipCode) + .orNull(); + } + +} diff --git a/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonOptionalDeliveryService.java b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonOptionalDeliveryService.java new file mode 100644 index 0000000000..586f0009ea --- /dev/null +++ b/patterns-modules/design-patterns-behavioral-2/src/test/java/com/baeldung/nullconversion/service/OnePersonOptionalDeliveryService.java @@ -0,0 +1,26 @@ +package com.baeldung.nullconversion.service; + +import com.baeldung.nullconversion.Address; +import com.baeldung.nullconversion.Delivery; +import com.baeldung.nullconversion.Person; +import com.baeldung.nullconversion.ZipCode; +import java.util.Optional; + +public class OnePersonOptionalDeliveryService extends MockOnePersonDeliveryServiceBase { + + + public OnePersonOptionalDeliveryService(Person person) { + super(person); + } + + @Override + public Delivery calculateDeliveryForPerson(Long id) { + return Optional.ofNullable(getPersonById(id)) + .map(Person::getAddress) + .map(Address::getZipCode) + .map(ZipCode::getCode) + .map(this::calculateDeliveryForZipCode) + .orElse(null); + } + +}