Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gp 155/random field comparator/completed #137

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions 3-0-java-core/3-6-4-random-field-comparator/README.MD
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>
15 changes: 15 additions & 0 deletions 3-0-java-core/3-6-4-random-field-comparator/pom.xml
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>
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);
}
}
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;
}
}
1 change: 1 addition & 0 deletions 3-0-java-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<module>3-6-1-file-reader</module>
<module>3-6-2-file-stats</module>
<module>3-6-3-crazy-regex</module>
<module>3-6-4-random-field-comparator</module>
</modules>


Expand Down