From f21dd68f66af89b7fbe92bc1073b9987b81ae6bd Mon Sep 17 00:00:00 2001 From: Stanislav-Zabramnyi Date: Tue, 9 Aug 2022 20:55:27 +0200 Subject: [PATCH 1/4] GP-155: add random-field-comparator exercise --- .../3-6-4-random-field-comparator/README.MD | 17 ++ .../3-6-4-random-field-comparator/pom.xml | 15 ++ .../bobocode/se/RandomFieldComparator.java | 90 ++++++++ .../se/RandomFieldComparatorTest.java | 195 ++++++++++++++++++ 3-0-java-core/pom.xml | 1 + 5 files changed, 318 insertions(+) create mode 100644 3-0-java-core/3-6-4-random-field-comparator/README.MD create mode 100644 3-0-java-core/3-6-4-random-field-comparator/pom.xml create mode 100644 3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java create mode 100644 3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java diff --git a/3-0-java-core/3-6-4-random-field-comparator/README.MD b/3-0-java-core/3-6-4-random-field-comparator/README.MD new file mode 100644 index 00000000..c361e6aa --- /dev/null +++ b/3-0-java-core/3-6-4-random-field-comparator/README.MD @@ -0,0 +1,17 @@ +# Random Field Comparator +#### Improve your reflection-related skills implementing a random field comparator ๐Ÿ’ช + +### Objectives +* implement a logic of choosing a random field to use it for comparison of objects of provided type โœ… +* implement a mechanism to check if field type is `Comparable` โœ… +* implement a method `compare` that compares two objects by randomly-provided field โœ… +* extend a method `compare` to manage null field values following condition when null value grater than a non-null value โœ… +* implement method `getComparingFieldName` that retrieves the name of randomly-chosen comparing fieldโœ… +* implement method `toString` โœ… + +--- +#### ๐Ÿ†• First time here? โ€“ [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) +#### โžก๏ธ Have any feedback? โ€“ [Please fill the form ](https://forms.gle/u6kHcecFuzxV232LA) + +## +
\ No newline at end of file diff --git a/3-0-java-core/3-6-4-random-field-comparator/pom.xml b/3-0-java-core/3-6-4-random-field-comparator/pom.xml new file mode 100644 index 00000000..557dc1f3 --- /dev/null +++ b/3-0-java-core/3-6-4-random-field-comparator/pom.xml @@ -0,0 +1,15 @@ + + + + 3-0-java-core + com.bobocode + 1.0-SNAPSHOT + + 4.0.0 + + 3-6-4-random-field-comparator + + + \ No newline at end of file diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java new file mode 100644 index 00000000..438b4953 --- /dev/null +++ b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java @@ -0,0 +1,90 @@ +package com.bobocode.se; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; +import lombok.SneakyThrows; + +/** + * A generic comparator that is comparing a random field of the given class. The field is either primitive or + * {@link Comparable}. It is chosen during comparator instance creation and is used for all comparisons. + *

+ * If no field is available to compare, the constructor throws {@link IllegalArgumentException} + * + * @param the type of the objects that may be compared by this comparator + * + * @author Stanislav Zabramnyi + */ +public class RandomFieldComparator implements Comparator { + + private final Class targetType; + private final Field fieldToCompare; + + public RandomFieldComparator(Class targetType) { + Objects.requireNonNull(targetType); + this.targetType = targetType; + this.fieldToCompare = chooseFieldToCompare(targetType); + } + + /** + * Compares two objects of the class T by the value of the field that was randomly chosen. It allows null values + * for the fields, and it treats null value grater than a non-null value. + * + * @param o1 + * @param o2 + * @return 1 in case of first parameter {@param o1} is greater than second one {@param o2}, + * 0 if objects are equals, + * -1 in case of first parameter {@param o1} is less than second one {@param o2}. + */ + @Override + @SneakyThrows + @SuppressWarnings("unchecked") + public int compare(T o1, T o2) { + Objects.requireNonNull(o1); + Objects.requireNonNull(o2); + fieldToCompare.setAccessible(true); + + var field1 = (Comparable) fieldToCompare.get(o1); + var field2 = (Comparable) fieldToCompare.get(o2); + + if (field1 == null && field2 == null) { + return 0; + } else if (field1 == null) { + return 1; + } else if (field2 == null) { + return -1; + } else { + return field1.compareTo(field2); + } + } + + /** + * Returns the name of the randomly-chosen comparing field. + */ + public String getComparingFieldName() { + return fieldToCompare.getName(); + } + + /** + * Returns a statement "Random field comparator of class '%s' is comparing '%s'" where the first param is the name + * of the type T, and the second parameter is the comparing field name. + * + * @return a predefined statement + */ + @Override + public String toString() { + return String.format("Random field comparator of class '%s' is comparing '%s'", targetType.getSimpleName(), + getComparingFieldName()); + } + + private Field chooseFieldToCompare(Class targetType) { + return Arrays.stream(targetType.getDeclaredFields()) + .filter(f -> isComparableType(f.getType())) + .findAny().orElseThrow(() -> new IllegalArgumentException("There are no fields available to compare")); + } + + private boolean isComparableType(Class type) { + return Arrays.asList(type.getInterfaces()).contains(Comparable.class); + } +} diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java b/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java new file mode 100644 index 00000000..4c9f853a --- /dev/null +++ b/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java @@ -0,0 +1,195 @@ +package com.bobocode.se; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.bobocode.data.Accounts; +import com.bobocode.model.Account; +import java.lang.reflect.Field; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +@TestMethodOrder(OrderAnnotation.class) +class RandomFieldComparatorTest { + + private final RandomFieldComparator randomFieldComparator = new RandomFieldComparator<>(Account.class); + + @Test + @Order(1) + @DisplayName("constructor should throw exception if parameter is null") + void classDoesNotApplyNullInConstructor() { + assertThrows(NullPointerException.class, () -> new RandomFieldComparator<>(null)); + } + + @Test + @Order(2) + @DisplayName("constructor should throw exception if provided type does not contain any fields") + void constructorThrowsExceptionIfNoFieldsInProvidedType() { + assertThrows(IllegalArgumentException.class, () -> new RandomFieldComparator<>(EmptyClass.class)); + } + + @Test + @Order(3) + @SneakyThrows + @DisplayName("target type must contain at least one field which implements Comparable interface") + void constructorThrowsExceptionIfNoComparableFieldsInProvidedType() { + assertThrows(IllegalArgumentException.class, () -> new RandomFieldComparator<>(ClassWithNotComparableField.class)); + } + + @Test + @Order(4) + @DisplayName("'compare' should throw exception if any parameter is null") + void compareWhenFirstParameterAreNull() { + + assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(null, new Account())); + assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(new Account(), null)); + } + + @Test + @Order(5) + @DisplayName("'compare' should return 0 if field values of both objects are null") + void compareWhenBothFieldValuesIsNull() { + var emptyAccount1 = new Account(); + var emptyAccount2 = new Account(); + //set balance null as balance field has a default value + emptyAccount1.setBalance(null); + emptyAccount2.setBalance(null); + + int compareResult = randomFieldComparator.compare(new Account(), new Account()); + + assertEquals(0, compareResult); + } + + @Test + @Order(6) + @DisplayName("'compare' treats null value greater than a non-null value, so it should return 1 if field value of " + + "first object is null") + void compareWhenFieldValuesOfFirstObjectIsNull() { + Account account = Accounts.generateAccount(); + account.setId(1L); + Account emptyAccount = new Account(); + emptyAccount.setBalance(null); + int compareResult = randomFieldComparator.compare(emptyAccount, account); + + assertEquals(1, compareResult); + } + + @Test + @Order(7) + @DisplayName("'compare' treats null value greater than a non-null value, so it should return -1 if field value of " + + "second object is null") + void compareWhenFieldValuesOfSecondObjectIsNull() { + Account account = Accounts.generateAccount(); + account.setId(1L); + Account emptyAccount = new Account(); + emptyAccount.setBalance(null); + int compareResult = randomFieldComparator.compare(account, emptyAccount); + + assertEquals(-1, compareResult); + } + + @Test + @Order(8) + @SneakyThrows + @DisplayName("'compare' should return 1 if field value of first object is greater") + void compareWhenFieldValueOfFirstObjectIsGrater() { + var fieldToCompareName = "firstName"; + Account account1 = Accounts.generateAccount(); + Account account2 = Accounts.generateAccount(); + Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); + fieldToCompareAccount.setAccessible(true); + + fieldToCompareAccount.set(account1, "Bob"); + fieldToCompareAccount.set(account2, "Alice"); + + setFieldToCompare(fieldToCompareName, Account.class); + int compareResult = randomFieldComparator.compare(account1, account2); + + assertEquals(1, compareResult); + } + + @Test + @Order(9) + @SneakyThrows + @DisplayName("'compare' should return -1 if field value of second object is greater") + void compareWhenFieldValueOfSecondObjectIsGrater() { + var fieldToCompareName = "firstName"; + Account account1 = Accounts.generateAccount(); + Account account2 = Accounts.generateAccount(); + Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); + fieldToCompareAccount.setAccessible(true); + + fieldToCompareAccount.set(account1, "Alice"); + fieldToCompareAccount.set(account2, "Bob"); + + setFieldToCompare(fieldToCompareName, Account.class); + int compareResult = randomFieldComparator.compare(account1, account2); + + assertEquals(-1, compareResult); + } + + @Test + @Order(10) + @SneakyThrows + @DisplayName("'compare' should return -1 if field value of second object is greater") + void compareWhenFieldValuesOfObjectsAreEqual() { + var fieldToCompareName = "firstName"; + Account account1 = Accounts.generateAccount(); + Account account2 = Accounts.generateAccount(); + Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); + fieldToCompareAccount.setAccessible(true); + + fieldToCompareAccount.set(account1, "Carol"); + fieldToCompareAccount.set(account2, "Carol"); + + setFieldToCompare(fieldToCompareName, Account.class); + int compareResult = randomFieldComparator.compare(account1, account2); + + assertEquals(0, compareResult); + } + + @Test + @Order(11) + @SneakyThrows + @DisplayName("'getComparingFieldName' should return the name of randomly-chosen field to be compared") + void getComparingFieldName() { + var fieldToCompareName = "lastName"; + setFieldToCompare(fieldToCompareName, Account.class); + + assertEquals(fieldToCompareName, randomFieldComparator.getComparingFieldName()); + } + + @Test + @Order(12) + @SneakyThrows + @DisplayName("'toString' should return the string - \"Random field comparator of class '%s' is comparing '%s'\"") + void toStringOverriding() { + var expectedString = "Random field comparator of class 'Account' is comparing 'email'"; + var fieldToCompareName = "email"; + setFieldToCompare(fieldToCompareName, Account.class); + + assertEquals(expectedString, randomFieldComparator.toString()); + } + + @SneakyThrows + private void setFieldToCompare(String fieldName, Class classType) { + Field fieldToCompare = randomFieldComparator.getClass().getDeclaredField("fieldToCompare"); + fieldToCompare.setAccessible(true); + fieldToCompare.set(randomFieldComparator, classType.getDeclaredField(fieldName)); + } + + private static class EmptyClass { + + } + + @AllArgsConstructor + private static class ClassWithNotComparableField { + + private Object field; + } +} \ No newline at end of file diff --git a/3-0-java-core/pom.xml b/3-0-java-core/pom.xml index 792d82a8..917e0cf1 100644 --- a/3-0-java-core/pom.xml +++ b/3-0-java-core/pom.xml @@ -14,6 +14,7 @@ 3-6-1-file-reader 3-6-2-file-stats 3-6-3-crazy-regex + 3-6-4-random-field-comparator From 4b6900619bf24e7737afa8ed2cfcbc122392c09f Mon Sep 17 00:00:00 2001 From: Stanislav-Zabramnyi Date: Wed, 10 Aug 2022 17:00:49 +0200 Subject: [PATCH 2/4] GP-155: refactoring(to be squashed) --- .../bobocode/se/RandomFieldComparator.java | 55 ++---- .../se/RandomFieldComparatorTest.java | 168 ++++++++++++------ 2 files changed, 124 insertions(+), 99 deletions(-) diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java index 438b4953..dc74b431 100644 --- a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java +++ b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java @@ -1,10 +1,7 @@ package com.bobocode.se; -import java.lang.reflect.Field; -import java.util.Arrays; +import com.bobocode.util.ExerciseNotCompletedException; import java.util.Comparator; -import java.util.Objects; -import lombok.SneakyThrows; /** * A generic comparator that is comparing a random field of the given class. The field is either primitive or @@ -13,18 +10,16 @@ * If no field is available to compare, the constructor throws {@link IllegalArgumentException} * * @param the type of the objects that may be compared by this comparator + *

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

* * @author Stanislav Zabramnyi */ public class RandomFieldComparator implements Comparator { - private final Class targetType; - private final Field fieldToCompare; - public RandomFieldComparator(Class targetType) { - Objects.requireNonNull(targetType); - this.targetType = targetType; - this.fieldToCompare = chooseFieldToCompare(targetType); + throw new ExerciseNotCompletedException(); // todo: implement this constructor; } /** @@ -33,37 +28,20 @@ public RandomFieldComparator(Class targetType) { * * @param o1 * @param o2 - * @return 1 in case of first parameter {@param o1} is greater than second one {@param o2}, - * 0 if objects are equals, - * -1 in case of first parameter {@param o1} is less than second one {@param o2}. + * @return positive int in case of first parameter {@param o1} is greater than second one {@param o2}, + * zero if objects are equals, + * negative int in case of first parameter {@param o1} is less than second one {@param o2}. */ @Override - @SneakyThrows - @SuppressWarnings("unchecked") public int compare(T o1, T o2) { - Objects.requireNonNull(o1); - Objects.requireNonNull(o2); - fieldToCompare.setAccessible(true); - - var field1 = (Comparable) fieldToCompare.get(o1); - var field2 = (Comparable) fieldToCompare.get(o2); - - if (field1 == null && field2 == null) { - return 0; - } else if (field1 == null) { - return 1; - } else if (field2 == null) { - return -1; - } else { - return field1.compareTo(field2); - } + throw new ExerciseNotCompletedException(); // todo: implement this method; } /** * Returns the name of the randomly-chosen comparing field. */ public String getComparingFieldName() { - return fieldToCompare.getName(); + throw new ExerciseNotCompletedException(); // todo: implement this method; } /** @@ -74,17 +52,6 @@ public String getComparingFieldName() { */ @Override public String toString() { - return String.format("Random field comparator of class '%s' is comparing '%s'", targetType.getSimpleName(), - getComparingFieldName()); - } - - private Field chooseFieldToCompare(Class targetType) { - return Arrays.stream(targetType.getDeclaredFields()) - .filter(f -> isComparableType(f.getType())) - .findAny().orElseThrow(() -> new IllegalArgumentException("There are no fields available to compare")); - } - - private boolean isComparableType(Class type) { - return Arrays.asList(type.getInterfaces()).contains(Comparable.class); + throw new ExerciseNotCompletedException(); // todo: implement this method; } } diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java b/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java index 4c9f853a..1733568e 100644 --- a/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java +++ b/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java @@ -1,12 +1,13 @@ package com.bobocode.se; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.bobocode.data.Accounts; -import com.bobocode.model.Account; import java.lang.reflect.Field; +import java.util.Arrays; import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; import lombok.SneakyThrows; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; @@ -21,29 +22,22 @@ class RandomFieldComparatorTest { @Test @Order(1) - @DisplayName("constructor should throw exception if parameter is null") + @DisplayName("Constructor throws an exception when parameter is null") void classDoesNotApplyNullInConstructor() { assertThrows(NullPointerException.class, () -> new RandomFieldComparator<>(null)); } @Test @Order(2) - @DisplayName("constructor should throw exception if provided type does not contain any fields") - void constructorThrowsExceptionIfNoFieldsInProvidedType() { - assertThrows(IllegalArgumentException.class, () -> new RandomFieldComparator<>(EmptyClass.class)); - } - - @Test - @Order(3) @SneakyThrows - @DisplayName("target type must contain at least one field which implements Comparable interface") + @DisplayName("Constructor throws an exception when the target type has no Comparable fields") void constructorThrowsExceptionIfNoComparableFieldsInProvidedType() { assertThrows(IllegalArgumentException.class, () -> new RandomFieldComparator<>(ClassWithNotComparableField.class)); } @Test - @Order(4) - @DisplayName("'compare' should throw exception if any parameter is null") + @Order(3) + @DisplayName("Method 'compare' throws an exception when any parameter is null") void compareWhenFirstParameterAreNull() { assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(null, new Account())); @@ -51,56 +45,47 @@ void compareWhenFirstParameterAreNull() { } @Test - @Order(5) - @DisplayName("'compare' should return 0 if field values of both objects are null") + @Order(4) + @DisplayName("Method 'compare' returns 0 when field values of both objects are null") void compareWhenBothFieldValuesIsNull() { - var emptyAccount1 = new Account(); - var emptyAccount2 = new Account(); - //set balance null as balance field has a default value - emptyAccount1.setBalance(null); - emptyAccount2.setBalance(null); - + setFieldToCompare("lastName", Account.class); int compareResult = randomFieldComparator.compare(new Account(), new Account()); - assertEquals(0, compareResult); + assertThat(compareResult).isZero(); } @Test - @Order(6) - @DisplayName("'compare' treats null value greater than a non-null value, so it should return 1 if field value of " + - "first object is null") + @Order(5) + @DisplayName("Method compare returns positive int when the first field value is null") void compareWhenFieldValuesOfFirstObjectIsNull() { - Account account = Accounts.generateAccount(); - account.setId(1L); Account emptyAccount = new Account(); - emptyAccount.setBalance(null); + Account account = new Account("Sibma", "LoinKing", "simba-bimba@gmail.com", 14); + setFieldToCompare("email", Account.class);//set field to compare explicitly as there are int field which has default value 0 int compareResult = randomFieldComparator.compare(emptyAccount, account); - assertEquals(1, compareResult); + assertThat(compareResult).isPositive(); } @Test - @Order(7) - @DisplayName("'compare' treats null value greater than a non-null value, so it should return -1 if field value of " + - "second object is null") + @Order(6) + @DisplayName("Method compare returns negative int when the second field value is null") void compareWhenFieldValuesOfSecondObjectIsNull() { - Account account = Accounts.generateAccount(); - account.setId(1L); + Account account = new Account("Mufasa", "LoinKing", "simba-bimba@gmail.com", 47); Account emptyAccount = new Account(); - emptyAccount.setBalance(null); + setFieldToCompare("firstName", Account.class); int compareResult = randomFieldComparator.compare(account, emptyAccount); - assertEquals(-1, compareResult); + assertThat(compareResult).isNegative(); } @Test - @Order(8) + @Order(7) @SneakyThrows - @DisplayName("'compare' should return 1 if field value of first object is greater") + @DisplayName("Method 'compare' returns positive int when the first value is greater") void compareWhenFieldValueOfFirstObjectIsGrater() { var fieldToCompareName = "firstName"; - Account account1 = Accounts.generateAccount(); - Account account2 = Accounts.generateAccount(); + Account account1 = new Account(); + Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); @@ -110,17 +95,17 @@ void compareWhenFieldValueOfFirstObjectIsGrater() { setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); - assertEquals(1, compareResult); + assertThat(compareResult).isPositive(); } @Test - @Order(9) + @Order(8) @SneakyThrows - @DisplayName("'compare' should return -1 if field value of second object is greater") + @DisplayName("Method 'compare' returns negative int when the first value is smaller") void compareWhenFieldValueOfSecondObjectIsGrater() { var fieldToCompareName = "firstName"; - Account account1 = Accounts.generateAccount(); - Account account2 = Accounts.generateAccount(); + Account account1 = new Account(); + Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); @@ -130,17 +115,17 @@ void compareWhenFieldValueOfSecondObjectIsGrater() { setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); - assertEquals(-1, compareResult); + assertThat(compareResult).isNegative(); } @Test - @Order(10) + @Order(9) @SneakyThrows - @DisplayName("'compare' should return -1 if field value of second object is greater") + @DisplayName("Method 'compare' returns zero when the field values are equal") void compareWhenFieldValuesOfObjectsAreEqual() { var fieldToCompareName = "firstName"; - Account account1 = Accounts.generateAccount(); - Account account2 = Accounts.generateAccount(); + Account account1 = new Account(); + Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); @@ -150,13 +135,73 @@ void compareWhenFieldValuesOfObjectsAreEqual() { setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); - assertEquals(0, compareResult); + assertThat(compareResult).isZero(); + } + + @Test + @Order(10) + @SneakyThrows + @DisplayName("Method 'compare' returns positive int when the first primitive value is greater") + void comparePrimitivesWhenFieldValueOfFirstObjectIsGrater() { + var fieldToCompareName = "age"; + Account account1 = new Account(); + Account account2 = new Account(); + Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); + fieldToCompareAccount.setAccessible(true); + + fieldToCompareAccount.setInt(account1, 7); + fieldToCompareAccount.setInt(account2, 3); + + setFieldToCompare(fieldToCompareName, Account.class); + int compareResult = randomFieldComparator.compare(account1, account2); + + assertThat(compareResult).isPositive(); } @Test @Order(11) @SneakyThrows - @DisplayName("'getComparingFieldName' should return the name of randomly-chosen field to be compared") + @DisplayName("Method 'compare' returns zero when the primitive field values are equal") + void comparePrimitivesWhenFieldValuesOfObjectsAreEqual() { + var fieldToCompareName = "age"; + Account account1 = new Account(); + Account account2 = new Account(); + Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); + fieldToCompareAccount.setAccessible(true); + + fieldToCompareAccount.setInt(account1, 15); + fieldToCompareAccount.setInt(account2, 15); + + setFieldToCompare(fieldToCompareName, Account.class); + int compareResult = randomFieldComparator.compare(account1, account2); + + assertThat(compareResult).isZero(); + } + + @Test + @Order(12) + @SneakyThrows + @DisplayName("Method 'compare' returns negative int when the first primitive value is smaller") + void comparePrimitivesWhenFieldValueOfSecondObjectIsGrater() { + var fieldToCompareName = "age"; + Account account1 = new Account(); + Account account2 = new Account(); + Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); + fieldToCompareAccount.setAccessible(true); + + fieldToCompareAccount.setInt(account1, 4); + fieldToCompareAccount.setInt(account2, 8); + + setFieldToCompare(fieldToCompareName, Account.class); + int compareResult = randomFieldComparator.compare(account1, account2); + + assertThat(compareResult).isNegative(); + } + + @Test + @Order(13) + @SneakyThrows + @DisplayName("Method 'getComparingFieldName' returns the name of randomly-chosen field to be compared") void getComparingFieldName() { var fieldToCompareName = "lastName"; setFieldToCompare(fieldToCompareName, Account.class); @@ -165,9 +210,9 @@ void getComparingFieldName() { } @Test - @Order(12) + @Order(14) @SneakyThrows - @DisplayName("'toString' should return the string - \"Random field comparator of class '%s' is comparing '%s'\"") + @DisplayName("Method toString is properly overridden") void toStringOverriding() { var expectedString = "Random field comparator of class 'Account' is comparing 'email'"; var fieldToCompareName = "email"; @@ -178,7 +223,10 @@ void toStringOverriding() { @SneakyThrows private void setFieldToCompare(String fieldName, Class classType) { - Field fieldToCompare = randomFieldComparator.getClass().getDeclaredField("fieldToCompare"); + Field fieldToCompare = Arrays.stream(randomFieldComparator.getClass().getDeclaredFields()) + .filter(f -> f.getType().equals(Field.class)) + .findAny() + .orElseThrow(); fieldToCompare.setAccessible(true); fieldToCompare.set(randomFieldComparator, classType.getDeclaredField(fieldName)); } @@ -192,4 +240,14 @@ private static class ClassWithNotComparableField { private Object field; } + + @NoArgsConstructor + @AllArgsConstructor + private static class Account { + + private String firstName; + private String lastName; + private String email; + private int age; + } } \ No newline at end of file From 0c522db0bf56f7c7f6bec5ea7a933e07650c7aa8 Mon Sep 17 00:00:00 2001 From: Stanislav-Zabramnyi Date: Wed, 10 Aug 2022 17:14:33 +0200 Subject: [PATCH 3/4] GP-155: add solution for add random-field-comparator exercise --- .../bobocode/se/RandomFieldComparator.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java index dc74b431..8c9ffee9 100644 --- a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java +++ b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java @@ -1,7 +1,11 @@ package com.bobocode.se; import com.bobocode.util.ExerciseNotCompletedException; +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.Comparator; +import java.util.Objects; +import lombok.SneakyThrows; /** * A generic comparator that is comparing a random field of the given class. The field is either primitive or @@ -18,8 +22,13 @@ */ public class RandomFieldComparator implements Comparator { + private final Class targetType; + private final Field fieldToCompare; + public RandomFieldComparator(Class targetType) { - throw new ExerciseNotCompletedException(); // todo: implement this constructor; + Objects.requireNonNull(targetType); + this.targetType = targetType; + this.fieldToCompare = chooseFieldToCompare(targetType); } /** @@ -32,16 +41,25 @@ public RandomFieldComparator(Class targetType) { * zero if objects are equals, * negative int in case of first parameter {@param o1} is less than second one {@param o2}. */ + @SneakyThrows + @SuppressWarnings("unchecked") @Override public int compare(T o1, T o2) { - throw new ExerciseNotCompletedException(); // todo: implement this method; + Objects.requireNonNull(o1); + Objects.requireNonNull(o2); + fieldToCompare.setAccessible(true); + + var field1 = (Comparable) fieldToCompare.get(o1); + var field2 = (Comparable) fieldToCompare.get(o2); + + return Comparator.nullsLast(Comparator.naturalOrder()).compare(field1, field2); } /** * Returns the name of the randomly-chosen comparing field. */ public String getComparingFieldName() { - throw new ExerciseNotCompletedException(); // todo: implement this method; + return fieldToCompare.getName(); } /** @@ -52,6 +70,13 @@ public String getComparingFieldName() { */ @Override public String toString() { - throw new ExerciseNotCompletedException(); // todo: implement this method; + return String.format("Random field comparator of class '%s' is comparing '%s'", targetType.getSimpleName(), + getComparingFieldName()); + } + + private Field chooseFieldToCompare(Class targetType) { + return Arrays.stream(targetType.getDeclaredFields()) + .filter(f -> Comparable.class.isAssignableFrom(f.getType()) || f.getType().isPrimitive()) + .findAny().orElseThrow(() -> new IllegalArgumentException("There are no fields available to compare")); } } From 65f705effad8479dc07666ed930d0560d4a3313f Mon Sep 17 00:00:00 2001 From: Stanislav-Zabramnyi Date: Thu, 11 Aug 2022 15:38:57 +0200 Subject: [PATCH 4/4] GP-155: refactoring --- .../bobocode/se/RandomFieldComparator.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java index 8c9ffee9..ee33f415 100644 --- a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java +++ b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java @@ -1,6 +1,7 @@ package com.bobocode.se; -import com.bobocode.util.ExerciseNotCompletedException; +import static java.util.Objects.requireNonNull; + import java.lang.reflect.Field; import java.util.Arrays; import java.util.Comparator; @@ -26,8 +27,7 @@ public class RandomFieldComparator implements Comparator { private final Field fieldToCompare; public RandomFieldComparator(Class targetType) { - Objects.requireNonNull(targetType); - this.targetType = targetType; + this.targetType = requireNonNull(targetType); this.fieldToCompare = chooseFieldToCompare(targetType); } @@ -41,18 +41,11 @@ public RandomFieldComparator(Class targetType) { * zero if objects are equals, * negative int in case of first parameter {@param o1} is less than second one {@param o2}. */ - @SneakyThrows - @SuppressWarnings("unchecked") @Override public int compare(T o1, T o2) { Objects.requireNonNull(o1); Objects.requireNonNull(o2); - fieldToCompare.setAccessible(true); - - var field1 = (Comparable) fieldToCompare.get(o1); - var field2 = (Comparable) fieldToCompare.get(o2); - - return Comparator.nullsLast(Comparator.naturalOrder()).compare(field1, field2); + return compareFieldValues(o1, o2); } /** @@ -79,4 +72,14 @@ private Field chooseFieldToCompare(Class targetType) { .filter(f -> Comparable.class.isAssignableFrom(f.getType()) || f.getType().isPrimitive()) .findAny().orElseThrow(() -> new IllegalArgumentException("There are no fields available to compare")); } + + @SneakyThrows + @SuppressWarnings("unchecked") + private > int compareFieldValues(T o1, T o2) { + fieldToCompare.setAccessible(true); + var value1 = (U) fieldToCompare.get(o1); + var value2 = (U) fieldToCompare.get(o2); + Comparator comparator = Comparator.nullsLast(Comparator.naturalOrder()); + return comparator.compare(value1, value2); + } }