From b9a7749ba7e9af1ba51f626406e44aa3821b64a0 Mon Sep 17 00:00:00 2001 From: Taras Boychuk Date: Fri, 23 Jun 2023 12:43:07 +0300 Subject: [PATCH] GP-146 Upgrade HeterogeneousMaxHolder exercise --- .../basics/HeterogeneousMaxHolder.java | 14 +- .../basics/HeterogeneousMaxHolderTest.java | 149 ++++++++++-------- 2 files changed, 94 insertions(+), 69 deletions(-) diff --git a/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java b/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java index f8b98b34..778fffcf 100644 --- a/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java +++ b/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java @@ -7,6 +7,12 @@ * key/value map, where the key is a type and the value is the maximum among all values of this type that were put. *

* It's based on the {@link Map} and provides an API that allows to put a value by type, and get a max value by type. + *

+ *

+ * TODO: to get the most out of your learning, visit our website + *

+ * + * @author Taras Boychuk */ public class HeterogeneousMaxHolder { @@ -17,7 +23,7 @@ public class HeterogeneousMaxHolder { * If the current max value is less than a provided one, or if it's null, then a provided value gets stored and the old * max is returned. Otherwise, nothing new is added, and the provided value is returned. *

- * So technically, this method always stored the greater value and returns the smaller one. + * So technically, this method always stores the greater value and returns the smaller one. * * @param key a provided value type * @param value a value to put @@ -27,9 +33,9 @@ public class HeterogeneousMaxHolder { // todo: implement a method according to javadoc /** - * An overloaded method put implements the same logic using a custom comparator. A given comparator is wrapped with - * a null-safe comparator, considering null smaller than any non-null object. - * + * An overloaded method put implements the same logic using a custom comparator. A given comparator is wrapped with + * a null-safe comparator, considering null smaller than any non-null object. + *

* All arguments must not be null. * * @param key a provided value type diff --git a/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/test/java/com/bobocode/basics/HeterogeneousMaxHolderTest.java b/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/test/java/com/bobocode/basics/HeterogeneousMaxHolderTest.java index 8bf9c7bd..da8844dc 100644 --- a/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/test/java/com/bobocode/basics/HeterogeneousMaxHolderTest.java +++ b/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/test/java/com/bobocode/basics/HeterogeneousMaxHolderTest.java @@ -8,9 +8,10 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import org.mockito.Mockito; import java.lang.reflect.Method; -import java.lang.reflect.Type; +import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; import java.util.Arrays; import java.util.Comparator; @@ -23,6 +24,7 @@ @TestMethodOrder(OrderAnnotation.class) class HeterogeneousMaxHolderTest { private HeterogeneousMaxHolder heterogeneousMaxHolder = new HeterogeneousMaxHolder(); + private HeterogeneousMaxHolder heterogeneousMaxHolderMock = Mockito.spy(HeterogeneousMaxHolder.class); @Test @Order(1) @@ -71,58 +73,53 @@ void putDeclaresOneTypeParam() { var putMethod = getPutMethod(); var methodTypeParameters = putMethod.getTypeParameters(); - assertThat(methodTypeParameters).hasSize(1); + + var typeParam = putMethod.getTypeParameters()[0]; + assertThat(typeParam.getName()).isEqualTo("T"); } @Test @Order(6) - @DisplayName("put method type parameter is called 'T'") - void putTypeParamIsCalledT() { + @DisplayName("put method accepts type-safe key") + void putMethodAcceptsTypeSafeKeyParameter() { var putMethod = getPutMethod(); - var typeParam = putMethod.getTypeParameters()[0]; + var typeParam = (ParameterizedType) putMethod.getGenericParameterTypes()[0]; + var typeArgument = typeParam.getActualTypeArguments()[0]; - assertThat(typeParam.getName()).isEqualTo("T"); + assertThat(typeParam.getRawType()).isEqualTo(Class.class); + assertThat(typeArgument.getTypeName()).isEqualTo("T"); } @Test @Order(7) - @DisplayName("type parameter 'T' is declared as Comparable") - void typeParamIsComparable() { + @DisplayName("put method accepts comparable value") + void putMethodAcceptsComparableValueParameter() { var putMethod = getPutMethod(); var typeParam = putMethod.getTypeParameters()[0]; - var bound = typeParam.getBounds()[0]; + var boundType = (ParameterizedType) typeParam.getBounds()[0]; - assertThat(bound.getTypeName()).isEqualTo(Comparable.class.getTypeName() + ""); + assertThat(boundType.getRawType()).isEqualTo(Comparable.class); } @Test @Order(8) - @SneakyThrows - @DisplayName("put method accepts type (class) and value (object) parameters") - void putHasKeyValueParameters() { - HeterogeneousMaxHolder.class.getMethod("put", Class.class, Comparable.class); - } + @DisplayName("put method supports value that has comparable super class") + void putMethodAcceptsValueParameterWithComparableSuperClass() { + var putMethod = getPutMethod(); - @Test - @Order(9) - @SneakyThrows - @DisplayName("put method params specify type arguments") - void putParametersSpecifyTypeArguments() { - var putMethod = HeterogeneousMaxHolder.class.getMethod("put", Class.class, Comparable.class); - var genericParamTypeNames = Arrays.stream(putMethod.getGenericParameterTypes()) - .map(Type::getTypeName) - .toList(); + var typeParam = putMethod.getTypeParameters()[0]; + var boundType = (ParameterizedType) typeParam.getBounds()[0]; + var typeArgument = boundType.getActualTypeArguments()[0].getTypeName(); - assertThat(genericParamTypeNames) - .contains(Class.class.getTypeName() + "") - .contains("T"); + assertThat(boundType.getRawType()).isEqualTo(Comparable.class); + assertThat(typeArgument).isEqualTo("? super T"); } @Test - @Order(10) + @Order(9) @SneakyThrows @DisplayName("put stores provided value when current max is null") void putStoresValueWhenCurrentMaxIsNull() { @@ -133,7 +130,7 @@ void putStoresValueWhenCurrentMaxIsNull() { } @Test - @Order(11) + @Order(10) @SneakyThrows @DisplayName("put returns null when current max is null") void putReturnsNullWhenCurrentMaxIsNull() { @@ -143,7 +140,7 @@ void putReturnsNullWhenCurrentMaxIsNull() { } @Test - @Order(12) + @Order(11) @SneakyThrows @DisplayName("put stores provided value when current max is smaller than it") void putStoresValueWhenCurrentMaxIsSmaller() { @@ -156,7 +153,7 @@ void putStoresValueWhenCurrentMaxIsSmaller() { } @Test - @Order(13) + @Order(12) @SneakyThrows @DisplayName("put returns old max value when the provided value is greater than it") void putReturnsOldMaxValue() { @@ -168,7 +165,7 @@ void putReturnsOldMaxValue() { } @Test - @Order(14) + @Order(13) @SneakyThrows @DisplayName("put ignores provided value when the current max is greater than it") void putIgnoresNewValueWhenCurrentMaxIsGreater() { @@ -181,7 +178,7 @@ void putIgnoresNewValueWhenCurrentMaxIsGreater() { } @Test - @Order(15) + @Order(14) @SneakyThrows @DisplayName("put returns provided value when the current max is greater than it") void putReturnsProvidedValueWhenCurrentMaxIsGreater() { @@ -193,45 +190,67 @@ void putReturnsProvidedValueWhenCurrentMaxIsGreater() { } @Test - @Order(16) + @Order(15) @SneakyThrows - @DisplayName("put method is overloaded with additional Comparator param") + @DisplayName("put method is overloaded with additional Comparator parameter") void putIsOverloadedWithAdditionalComparatorParam() { - var overloadedPutMethod = getOverloadedPutMethod(); - var params = overloadedPutMethod.getParameters(); + HeterogeneousMaxHolder.class.getMethod("put", Class.class, Object.class, Comparator.class); + } + + @Test + @Order(16) + @DisplayName("Overloaded put method declares one type parameter T") + void overloadedPutDeclaresOneTypeParam() { + var putMethod = getOverloadedPutMethod(); - assertThat(params[2].getType()).isEqualTo(Comparator.class); + var methodTypeParameters = putMethod.getTypeParameters(); + assertThat(methodTypeParameters).hasSize(1); + var typeParam = putMethod.getTypeParameters()[0]; + assertThat(typeParam.getName()).isEqualTo("T"); } @Test @Order(17) - @DisplayName("Overloaded put has simple type param 'T'") - void overloadedPutHasSimpleTypeParameterT() { - var overloadedPutMethod = getOverloadedPutMethod(); + @DisplayName("Overloaded put method accepts type-safe key") + void overloadedPutMethodAcceptsTypeSafeKeyParameter() { + var putMethod = getOverloadedPutMethod(); + + var typeParam = (ParameterizedType) putMethod.getGenericParameterTypes()[0]; + var typeArgument = typeParam.getActualTypeArguments()[0]; - assertThat(overloadedPutMethod.getTypeParameters()).hasSize(1); - assertThat(overloadedPutMethod.getTypeParameters()[0].getTypeName()).isEqualTo("T"); + assertThat(typeParam.getRawType()).isEqualTo(Class.class); + assertThat(typeArgument.getTypeName()).isEqualTo("T"); } @Test @Order(18) + @DisplayName("Overloaded put method accepts value of arbitrary type T") + void overloadedPutMethodAcceptsAnyValue() { + var putMethod = getOverloadedPutMethod(); + + var genericValueTypeParam = putMethod.getGenericParameterTypes()[1]; + var actualValueTypeParm = putMethod.getParameterTypes()[1]; + + assertThat(genericValueTypeParam.getTypeName()).isEqualTo("T"); + assertThat(actualValueTypeParm).isEqualTo(Object.class); + } + + @Test + @Order(19) @SneakyThrows - @DisplayName("Overloaded put method params specify type arguments") - void overloadedPutParametersSpecifyTypeArguments() { + @DisplayName("Overloaded put method supports comparator of a super type") + void overloadedPutAcceptsComparatorOfSuperTypes() { var putMethod = HeterogeneousMaxHolder.class.getMethod("put", Class.class, Object.class, Comparator.class); - var genericParamTypeNames = Arrays.stream(putMethod.getGenericParameterTypes()) - .map(Type::getTypeName) - .toList(); - assertThat(genericParamTypeNames) - .contains(Class.class.getTypeName() + "") - .contains("T") - .contains(Comparator.class.getTypeName() + ""); + var comparatorParam = (ParameterizedType) putMethod.getGenericParameterTypes()[2]; + var comparatorTypeArgument = comparatorParam.getActualTypeArguments()[0]; + + assertThat(comparatorTypeArgument.getTypeName()).isEqualTo("? super T"); } @Test - @Order(19) + @Order(20) @SneakyThrows @DisplayName("Overloaded put stores provided value when current max is null") void overloadedPutStoresValueWhenCurrentMaxIsNull() { @@ -243,7 +262,7 @@ void overloadedPutStoresValueWhenCurrentMaxIsNull() { } @Test - @Order(20) + @Order(21) @SneakyThrows @DisplayName("Overloaded put returns null when current max is null") void overloadedPutReturnsNullWhenCurrentMaxIsNull() { @@ -253,7 +272,7 @@ void overloadedPutReturnsNullWhenCurrentMaxIsNull() { } @Test - @Order(21) + @Order(22) @SneakyThrows @DisplayName("Overloaded put stores provided value when current max is smaller than it") void overloadedPutStoresValueWhenCurrentMaxIsSmaller() { @@ -269,7 +288,7 @@ void overloadedPutStoresValueWhenCurrentMaxIsSmaller() { } @Test - @Order(22) + @Order(23) @SneakyThrows @DisplayName("Overloaded put returns old max value when the provided value is greater than it") void overloadedPutReturnsOldMaxValue() { @@ -284,7 +303,7 @@ void overloadedPutReturnsOldMaxValue() { } @Test - @Order(23) + @Order(24) @SneakyThrows @DisplayName("Overloaded put ignores provided value when the current max is greater than it") void overloadedPutIgnoresNewValueWhenCurrentMaxIsGreater() { @@ -300,7 +319,7 @@ void overloadedPutIgnoresNewValueWhenCurrentMaxIsGreater() { } @Test - @Order(24) + @Order(25) @SneakyThrows @DisplayName("Overloaded put returns provided value when the current max is greater") void overloadedPutReturnsProvidedValueWhenCurrentMaxIsGreater() { @@ -315,7 +334,7 @@ void overloadedPutReturnsProvidedValueWhenCurrentMaxIsGreater() { } @Test - @Order(25) + @Order(26) @DisplayName("getMax method exists") void getMaxExists() { var getMaxMethodExists = Arrays.stream(HeterogeneousMaxHolder.class.getDeclaredMethods()) @@ -325,7 +344,7 @@ void getMaxExists() { } @Test - @Order(26) + @Order(27) @DisplayName("getMax declares one simple type param 'T'") void getMaxDeclaresOneTypeParam() { var getMaxMethod = getGetMaxMethod(); @@ -337,7 +356,7 @@ void getMaxDeclaresOneTypeParam() { } @Test - @Order(27) + @Order(28) @DisplayName("getMax has one parameter") void getMaxHasOneParameter() { var getMaxMethod = getGetMaxMethod(); @@ -349,7 +368,7 @@ void getMaxHasOneParameter() { } @Test - @Order(28) + @Order(29) @DisplayName("getMax param specifies type arguments") void getMaxParamSpecifyTypeArguments() { var getMaxMethod = getGetMaxMethod(); @@ -360,7 +379,7 @@ void getMaxParamSpecifyTypeArguments() { } @Test - @Order(29) + @Order(30) @DisplayName("getMax returns value when it exists") void getMaxReturnsValueWhenItExists() { givenMaxHolderWithData(String.class, "I am maximum"); @@ -371,7 +390,7 @@ void getMaxReturnsValueWhenItExists() { } @Test - @Order(30) + @Order(31) @DisplayName("getMax returns value when it exists") void getMaxReturnsNullWhenNoValueByGivenTypeExists() { var returnedValue = callGetMax(String.class); @@ -380,7 +399,7 @@ void getMaxReturnsNullWhenNoValueByGivenTypeExists() { } @Test - @Order(31) + @Order(32) @DisplayName("HeterogeneousMaxHolder keeps track of value one per each type") void maxHolderKeepsTrackOfMultipleValuesPerType() { callPut(String.class, "A");