Search code examples
javasortingreflectioncomparator

Check if Collection of DTOs sorted by a given field


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?

Unparameterized use of Comparable.


Solution

  • 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.