I have to write tests for a pagination and sorting GET ALL endpoint that I have. The following is the simple DTO that I am working with:
public class TransactionDto {
private Long id;
private Long sourceAccountId;
private Long targetAccountId;
private BigDecimal amount;
private Currency currency;
}
Of importance to this discussion is that all of those fields are Comparable
, including the type Currency
, which is an enum
I have created with about 300 different currency symbols.
Now, I want to ensure that a REST controller endpoint has returned a Collection
of these TransactionDto
instances in a sorted manner. After reading here I learned of Guava's Comparators
class and, specifically, the isInOrder
method (doc link).
I have to begin with the field name of TransactionDto
to sort by, and I also have this simple SortOrder
enum to designate ascending or descending sort order:
public enum SortOrder {
ASC, DESC
}
This is what I have so far, and I am stuck on the fact that the Method.invoke()
method returns raw Object
instances.
private boolean collectionIsSortedByFieldInGivenDirection(Collection<TransactionDto> transactionDtos,
String sortByField, SortOrder sortOrder){
return Comparators.isInOrder(transactionDtos, (t1, t2) -> compareFieldsInGivenOrder(t1, t2, sortByField, sortOrder));
}
private int compareFieldsInGivenOrder(TransactionDto transactionOne, TransactionDto transactionTwo,
String sortByField, SortOrder sortOrder){
try {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(sortByField, TransactionDto.class);
Method appropriateGetter = propertyDescriptor.getReadMethod();
Object transactionOneValue = appropriateGetter.invoke(transactionOne);
Object transactionTwoValue = appropriateGetter.invoke(transactionTwo);
// Not much I can do with raw Objects...
} catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
}
}
I need to somehow retrieve variables of the appropriate type, which, as shown above, will be a type whose instances are Comparable
with instances of the same type. I have also fiddled a bit with the Field.getType()
method after reading through this answer but I haven't had much luck. Any ideas?
// Edit: Addressing @0xh3xa's comment about the casting to Comparable
. The code compiles as long as the Comparable
type is unparameterized, but of course IntelliJ warns that the use of the type is raw, unparameterized. Perhaps a more elegant solution would be available?
You can solve this by making the method generic and adding casts:
private <T extends Comparable<T>> int compareFieldsInGivenOrder(TransactionDto transactionOne, TransactionDto transactionTwo,
String sortByField, SortOrder sortOrder){
try {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(sortByField, TransactionDto.class);
Method appropriateGetter = propertyDescriptor.getReadMethod();
@SuppressWarnings("unchecked")
T transactionOneValue = (T) appropriateGetter.invoke(transactionOne);
@SuppressWarnings("unchecked")
T transactionTwoValue = (T) appropriateGetter.invoke(transactionTwo);
return sortOrder == SortOrder.ASC ? transactionOneValue.compareTo(transactionTwoValue) :
transactionTwoValue.compareTo(transactionOneValue);
} catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
}
}
Note that this results in unchecked cast warnings, which this example suppresses. However, if this code is only ever called with Comparable
property names, it won't throw exceptions at runtime. Using reflection is inherently unchecked, so hopefully this is an acceptable compromise.