Skip to content

Commit 2700a1e

Browse files
GP-155: add random-field-comparator exercise (#135)
* GP-155: add random-field-comparator exercise
1 parent 92139dc commit 2700a1e

File tree

5 files changed

+343
-0
lines changed

5 files changed

+343
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Random Field Comparator
2+
#### Improve your reflection-related skills implementing a random field comparator 💪
3+
4+
### Objectives
5+
* implement a logic of choosing a random field to use it for comparison of objects of provided type ✅
6+
* implement a mechanism to check if field type is `Comparable`
7+
* implement a method `compare` that compares two objects by randomly-provided field ✅
8+
* extend a method `compare` to manage null field values following condition when null value greater than a non-null value ✅
9+
* implement method `getComparingFieldName` that retrieves the name of randomly-chosen comparing field✅
10+
* implement method `toString`
11+
12+
---
13+
#### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction)
14+
#### ➡️ Have any feedback? – [Please fill the form ](https://forms.gle/u6kHcecFuzxV232LA)
15+
16+
##
17+
<div align="center"><img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/animation/GitHub%20Star_3.gif" height=50/></div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>3-0-java-core</artifactId>
7+
<groupId>com.bobocode</groupId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>3-6-4-random-field-comparator</artifactId>
13+
14+
15+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.bobocode.se;
2+
3+
import com.bobocode.util.ExerciseNotCompletedException;
4+
import java.util.Comparator;
5+
6+
/**
7+
* A generic comparator that is comparing a random field of the given class. The field is either primitive or
8+
* {@link Comparable}. It is chosen during comparator instance creation and is used for all comparisons.
9+
* <p>
10+
* If no field is available to compare, the constructor throws {@link IllegalArgumentException}
11+
*
12+
* @param <T> the type of the objects that may be compared by this comparator
13+
*<p><p>
14+
* <strong>TODO: to get the most out of your learning, <a href="https://www.bobocode.com/learn">visit our website</a></strong>
15+
* <p>
16+
*
17+
* @author Stanislav Zabramnyi
18+
*/
19+
public class RandomFieldComparator<T> implements Comparator<T> {
20+
21+
public RandomFieldComparator(Class<T> targetType) {
22+
throw new ExerciseNotCompletedException(); // todo: implement this constructor;
23+
}
24+
25+
/**
26+
* Compares two objects of the class T by the value of the field that was randomly chosen. It allows null values
27+
* for the fields, and it treats null value greater than a non-null value.
28+
*
29+
* @param o1
30+
* @param o2
31+
* @return positive int in case of first parameter {@param o1} is greater than second one {@param o2},
32+
* zero if objects are equals,
33+
* negative int in case of first parameter {@param o1} is less than second one {@param o2}.
34+
*/
35+
@Override
36+
public int compare(T o1, T o2) {
37+
throw new ExerciseNotCompletedException(); // todo: implement this method;
38+
}
39+
40+
/**
41+
* Returns the name of the randomly-chosen comparing field.
42+
*/
43+
public String getComparingFieldName() {
44+
throw new ExerciseNotCompletedException(); // todo: implement this method;
45+
}
46+
47+
/**
48+
* Returns a statement "Random field comparator of class '%s' is comparing '%s'" where the first param is the name
49+
* of the type T, and the second parameter is the comparing field name.
50+
*
51+
* @return a predefined statement
52+
*/
53+
@Override
54+
public String toString() {
55+
throw new ExerciseNotCompletedException(); // todo: implement this method;
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
package com.bobocode.se;
2+
3+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
7+
import java.lang.reflect.Field;
8+
import java.util.Arrays;
9+
import lombok.AllArgsConstructor;
10+
import lombok.NoArgsConstructor;
11+
import lombok.SneakyThrows;
12+
import org.junit.jupiter.api.DisplayName;
13+
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
14+
import org.junit.jupiter.api.Order;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.TestMethodOrder;
17+
18+
@TestMethodOrder(OrderAnnotation.class)
19+
class RandomFieldComparatorTest {
20+
21+
private final RandomFieldComparator<Account> randomFieldComparator = new RandomFieldComparator<>(Account.class);
22+
23+
@Test
24+
@Order(1)
25+
@DisplayName("Constructor throws an exception when parameter is null")
26+
void classDoesNotApplyNullInConstructor() {
27+
assertThrows(NullPointerException.class, () -> new RandomFieldComparator<>(null));
28+
}
29+
30+
@Test
31+
@Order(2)
32+
@SneakyThrows
33+
@DisplayName("Constructor throws an exception when the target type has no Comparable fields")
34+
void constructorThrowsExceptionIfNoComparableFieldsInProvidedType() {
35+
assertThrows(IllegalArgumentException.class, () -> new RandomFieldComparator<>(ClassWithNotComparableField.class));
36+
}
37+
38+
@Test
39+
@Order(3)
40+
@DisplayName("Method 'compare' throws an exception when any parameter is null")
41+
void compareWhenFirstParameterAreNull() {
42+
43+
assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(null, new Account()));
44+
assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(new Account(), null));
45+
}
46+
47+
@Test
48+
@Order(4)
49+
@DisplayName("Method 'compare' returns 0 when field values of both objects are null")
50+
void compareWhenBothFieldValuesIsNull() {
51+
setFieldToCompare("lastName", Account.class);
52+
int compareResult = randomFieldComparator.compare(new Account(), new Account());
53+
54+
assertThat(compareResult).isZero();
55+
}
56+
57+
@Test
58+
@Order(5)
59+
@DisplayName("Method compare returns positive int when the first field value is null")
60+
void compareWhenFieldValuesOfFirstObjectIsNull() {
61+
Account emptyAccount = new Account();
62+
Account account = new Account("Sibma", "LoinKing", "[email protected]", 14);
63+
setFieldToCompare("email", Account.class);//set field to compare explicitly as there are int field which has default value 0
64+
int compareResult = randomFieldComparator.compare(emptyAccount, account);
65+
66+
assertThat(compareResult).isPositive();
67+
}
68+
69+
@Test
70+
@Order(6)
71+
@DisplayName("Method compare returns negative int when the second field value is null")
72+
void compareWhenFieldValuesOfSecondObjectIsNull() {
73+
Account account = new Account("Mufasa", "LoinKing", "[email protected]", 47);
74+
Account emptyAccount = new Account();
75+
setFieldToCompare("firstName", Account.class);
76+
int compareResult = randomFieldComparator.compare(account, emptyAccount);
77+
78+
assertThat(compareResult).isNegative();
79+
}
80+
81+
@Test
82+
@Order(7)
83+
@SneakyThrows
84+
@DisplayName("Method 'compare' returns positive int when the first value is greater")
85+
void compareWhenFieldValueOfFirstObjectIsGreater() {
86+
var fieldToCompareName = "firstName";
87+
Account account1 = new Account();
88+
Account account2 = new Account();
89+
Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
90+
fieldToCompareAccount.setAccessible(true);
91+
92+
fieldToCompareAccount.set(account1, "Bob");
93+
fieldToCompareAccount.set(account2, "Alice");
94+
95+
setFieldToCompare(fieldToCompareName, Account.class);
96+
int compareResult = randomFieldComparator.compare(account1, account2);
97+
98+
assertThat(compareResult).isPositive();
99+
}
100+
101+
@Test
102+
@Order(8)
103+
@SneakyThrows
104+
@DisplayName("Method 'compare' returns negative int when the first value is smaller")
105+
void compareWhenFieldValueOfSecondObjectIsGreater() {
106+
var fieldToCompareName = "firstName";
107+
Account account1 = new Account();
108+
Account account2 = new Account();
109+
Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
110+
fieldToCompareAccount.setAccessible(true);
111+
112+
fieldToCompareAccount.set(account1, "Alice");
113+
fieldToCompareAccount.set(account2, "Bob");
114+
115+
setFieldToCompare(fieldToCompareName, Account.class);
116+
int compareResult = randomFieldComparator.compare(account1, account2);
117+
118+
assertThat(compareResult).isNegative();
119+
}
120+
121+
@Test
122+
@Order(9)
123+
@SneakyThrows
124+
@DisplayName("Method 'compare' returns zero when the field values are equal")
125+
void compareWhenFieldValuesOfObjectsAreEqual() {
126+
var fieldToCompareName = "firstName";
127+
Account account1 = new Account();
128+
Account account2 = new Account();
129+
Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
130+
fieldToCompareAccount.setAccessible(true);
131+
132+
fieldToCompareAccount.set(account1, "Carol");
133+
fieldToCompareAccount.set(account2, "Carol");
134+
135+
setFieldToCompare(fieldToCompareName, Account.class);
136+
int compareResult = randomFieldComparator.compare(account1, account2);
137+
138+
assertThat(compareResult).isZero();
139+
}
140+
141+
@Test
142+
@Order(10)
143+
@SneakyThrows
144+
@DisplayName("Method 'compare' returns positive int when the first primitive value is greater")
145+
void comparePrimitivesWhenFieldValueOfFirstObjectIsGreater() {
146+
var fieldToCompareName = "age";
147+
Account account1 = new Account();
148+
Account account2 = new Account();
149+
Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
150+
fieldToCompareAccount.setAccessible(true);
151+
152+
fieldToCompareAccount.setInt(account1, 7);
153+
fieldToCompareAccount.setInt(account2, 3);
154+
155+
setFieldToCompare(fieldToCompareName, Account.class);
156+
int compareResult = randomFieldComparator.compare(account1, account2);
157+
158+
assertThat(compareResult).isPositive();
159+
}
160+
161+
@Test
162+
@Order(11)
163+
@SneakyThrows
164+
@DisplayName("Method 'compare' returns zero when the primitive field values are equal")
165+
void comparePrimitivesWhenFieldValuesOfObjectsAreEqual() {
166+
var fieldToCompareName = "age";
167+
Account account1 = new Account();
168+
Account account2 = new Account();
169+
Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
170+
fieldToCompareAccount.setAccessible(true);
171+
172+
fieldToCompareAccount.setInt(account1, 15);
173+
fieldToCompareAccount.setInt(account2, 15);
174+
175+
setFieldToCompare(fieldToCompareName, Account.class);
176+
int compareResult = randomFieldComparator.compare(account1, account2);
177+
178+
assertThat(compareResult).isZero();
179+
}
180+
181+
@Test
182+
@Order(12)
183+
@SneakyThrows
184+
@DisplayName("Method 'compare' returns negative int when the first primitive value is smaller")
185+
void comparePrimitivesWhenFieldValueOfSecondObjectIsGreater() {
186+
var fieldToCompareName = "age";
187+
Account account1 = new Account();
188+
Account account2 = new Account();
189+
Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
190+
fieldToCompareAccount.setAccessible(true);
191+
192+
fieldToCompareAccount.setInt(account1, 4);
193+
fieldToCompareAccount.setInt(account2, 8);
194+
195+
setFieldToCompare(fieldToCompareName, Account.class);
196+
int compareResult = randomFieldComparator.compare(account1, account2);
197+
198+
assertThat(compareResult).isNegative();
199+
}
200+
201+
@Test
202+
@Order(13)
203+
@SneakyThrows
204+
@DisplayName("Method 'getComparingFieldName' returns the name of randomly-chosen field to be compared")
205+
void getComparingFieldName() {
206+
var fieldToCompareName = "lastName";
207+
setFieldToCompare(fieldToCompareName, Account.class);
208+
209+
assertEquals(fieldToCompareName, randomFieldComparator.getComparingFieldName());
210+
}
211+
212+
@Test
213+
@Order(14)
214+
@SneakyThrows
215+
@DisplayName("Method toString is properly overridden")
216+
void toStringOverriding() {
217+
var expectedString = "Random field comparator of class 'Account' is comparing 'email'";
218+
var fieldToCompareName = "email";
219+
setFieldToCompare(fieldToCompareName, Account.class);
220+
221+
assertEquals(expectedString, randomFieldComparator.toString());
222+
}
223+
224+
@SneakyThrows
225+
private <T> void setFieldToCompare(String fieldName, Class<T> classType) {
226+
Field fieldToCompare = Arrays.stream(randomFieldComparator.getClass().getDeclaredFields())
227+
.filter(f -> f.getType().equals(Field.class))
228+
.findAny()
229+
.orElseThrow();
230+
fieldToCompare.setAccessible(true);
231+
fieldToCompare.set(randomFieldComparator, classType.getDeclaredField(fieldName));
232+
}
233+
234+
private static class EmptyClass {
235+
236+
}
237+
238+
@AllArgsConstructor
239+
private static class ClassWithNotComparableField {
240+
241+
private Object field;
242+
}
243+
244+
@NoArgsConstructor
245+
@AllArgsConstructor
246+
private static class Account {
247+
248+
private String firstName;
249+
private String lastName;
250+
private String email;
251+
private int age;
252+
}
253+
}

3-0-java-core/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<module>3-6-1-file-reader</module>
1515
<module>3-6-2-file-stats</module>
1616
<module>3-6-3-crazy-regex</module>
17+
<module>3-6-4-random-field-comparator</module>
1718
</modules>
1819

1920

0 commit comments

Comments
 (0)