-
-
Notifications
You must be signed in to change notification settings - Fork 464
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Gp 155/random field comparator/completed (#137)
* GP-155: add solution to random-field-comparator exercise
- Loading branch information
1 parent
51df2ed
commit ee914af
Showing
5 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>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) | ||
|
||
## | ||
<div align="center"><img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/animation/GitHub%20Star_3.gif" height=50/></div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>3-0-java-core</artifactId> | ||
<groupId>com.bobocode</groupId> | ||
<version>1.0-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>3-6-4-random-field-comparator</artifactId> | ||
|
||
|
||
</project> |
85 changes: 85 additions & 0 deletions
85
...re/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package com.bobocode.se; | ||
|
||
import static java.util.Objects.requireNonNull; | ||
|
||
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. | ||
* <p> | ||
* If no field is available to compare, the constructor throws {@link IllegalArgumentException} | ||
* | ||
* @param <T> the type of the objects that may be compared by this comparator | ||
*<p><p> | ||
* <strong>TODO: to get the most out of your learning, <a href="https://www.bobocode.com/learn">visit our website</a></strong> | ||
* <p> | ||
* | ||
* @author Stanislav Zabramnyi | ||
*/ | ||
public class RandomFieldComparator<T> implements Comparator<T> { | ||
|
||
private final Class<T> targetType; | ||
private final Field fieldToCompare; | ||
|
||
public RandomFieldComparator(Class<T> targetType) { | ||
this.targetType = requireNonNull(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 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 | ||
public int compare(T o1, T o2) { | ||
Objects.requireNonNull(o1); | ||
Objects.requireNonNull(o2); | ||
return compareFieldValues(o1, o2); | ||
} | ||
|
||
/** | ||
* 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<T> 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")); | ||
} | ||
|
||
@SneakyThrows | ||
@SuppressWarnings("unchecked") | ||
private <U extends Comparable<? super U>> int compareFieldValues(T o1, T o2) { | ||
fieldToCompare.setAccessible(true); | ||
var value1 = (U) fieldToCompare.get(o1); | ||
var value2 = (U) fieldToCompare.get(o2); | ||
Comparator<U> comparator = Comparator.nullsLast(Comparator.naturalOrder()); | ||
return comparator.compare(value1, value2); | ||
} | ||
} |
253 changes: 253 additions & 0 deletions
253
...-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
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 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; | ||
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<Account> randomFieldComparator = new RandomFieldComparator<>(Account.class); | ||
|
||
@Test | ||
@Order(1) | ||
@DisplayName("Constructor throws an exception when parameter is null") | ||
void classDoesNotApplyNullInConstructor() { | ||
assertThrows(NullPointerException.class, () -> new RandomFieldComparator<>(null)); | ||
} | ||
|
||
@Test | ||
@Order(2) | ||
@SneakyThrows | ||
@DisplayName("Constructor throws an exception when the target type has no Comparable fields") | ||
void constructorThrowsExceptionIfNoComparableFieldsInProvidedType() { | ||
assertThrows(IllegalArgumentException.class, () -> new RandomFieldComparator<>(ClassWithNotComparableField.class)); | ||
} | ||
|
||
@Test | ||
@Order(3) | ||
@DisplayName("Method 'compare' throws an exception when any parameter is null") | ||
void compareWhenFirstParameterAreNull() { | ||
|
||
assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(null, new Account())); | ||
assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(new Account(), null)); | ||
} | ||
|
||
@Test | ||
@Order(4) | ||
@DisplayName("Method 'compare' returns 0 when field values of both objects are null") | ||
void compareWhenBothFieldValuesIsNull() { | ||
setFieldToCompare("lastName", Account.class); | ||
int compareResult = randomFieldComparator.compare(new Account(), new Account()); | ||
|
||
assertThat(compareResult).isZero(); | ||
} | ||
|
||
@Test | ||
@Order(5) | ||
@DisplayName("Method compare returns positive int when the first field value is null") | ||
void compareWhenFieldValuesOfFirstObjectIsNull() { | ||
Account emptyAccount = new Account(); | ||
Account account = new Account("Sibma", "LoinKing", "[email protected]", 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); | ||
|
||
assertThat(compareResult).isPositive(); | ||
} | ||
|
||
@Test | ||
@Order(6) | ||
@DisplayName("Method compare returns negative int when the second field value is null") | ||
void compareWhenFieldValuesOfSecondObjectIsNull() { | ||
Account account = new Account("Mufasa", "LoinKing", "[email protected]", 47); | ||
Account emptyAccount = new Account(); | ||
setFieldToCompare("firstName", Account.class); | ||
int compareResult = randomFieldComparator.compare(account, emptyAccount); | ||
|
||
assertThat(compareResult).isNegative(); | ||
} | ||
|
||
@Test | ||
@Order(7) | ||
@SneakyThrows | ||
@DisplayName("Method 'compare' returns positive int when the first value is greater") | ||
void compareWhenFieldValueOfFirstObjectIsGrater() { | ||
var fieldToCompareName = "firstName"; | ||
Account account1 = new Account(); | ||
Account account2 = new Account(); | ||
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); | ||
|
||
assertThat(compareResult).isPositive(); | ||
} | ||
|
||
@Test | ||
@Order(8) | ||
@SneakyThrows | ||
@DisplayName("Method 'compare' returns negative int when the first value is smaller") | ||
void compareWhenFieldValueOfSecondObjectIsGrater() { | ||
var fieldToCompareName = "firstName"; | ||
Account account1 = new Account(); | ||
Account account2 = new Account(); | ||
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); | ||
|
||
assertThat(compareResult).isNegative(); | ||
} | ||
|
||
@Test | ||
@Order(9) | ||
@SneakyThrows | ||
@DisplayName("Method 'compare' returns zero when the field values are equal") | ||
void compareWhenFieldValuesOfObjectsAreEqual() { | ||
var fieldToCompareName = "firstName"; | ||
Account account1 = new Account(); | ||
Account account2 = new Account(); | ||
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); | ||
|
||
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("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); | ||
|
||
assertEquals(fieldToCompareName, randomFieldComparator.getComparingFieldName()); | ||
} | ||
|
||
@Test | ||
@Order(14) | ||
@SneakyThrows | ||
@DisplayName("Method toString is properly overridden") | ||
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 <T> void setFieldToCompare(String fieldName, Class<T> classType) { | ||
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)); | ||
} | ||
|
||
private static class EmptyClass { | ||
|
||
} | ||
|
||
@AllArgsConstructor | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters