Imran Jawaid
Imran Jawaid

Reputation: 471

Dynamically sort the list based on user selected property in java8

I have this method which works fine but is there any cleaner way to do it?

Currently I have added comparators to the map and get the right comparator based on user-selected value.

private Comparator<? super BusinessPartnerAssignmentDetail> getComparator(PortfolioFilterDto portfolioFilterDto){
    Map<String, Comparator<? super BusinessPartnerAssignmentDetail>> sortingOptions = new HashMap<>();
    sortingOptions.put("fieldOfficeDescription", Comparator.comparing(BusinessPartnerAssignmentDetail::getFieldOfficeDescription, Comparator.nullsLast(Comparator.naturalOrder())));
    sortingOptions.put("locationDescription", Comparator.comparing(BusinessPartnerAssignmentDetail::getLocationDescription, Comparator.nullsLast(Comparator.reverseOrder())));
    sortingOptions.put("segmentType", Comparator.comparing(BusinessPartnerAssignmentDetail::getSegmentType, Comparator.nullsLast(Comparator.reverseOrder())));
    sortingOptions.put("displayName", Comparator.comparing(BusinessPartnerAssignmentDetail::getDisplayName, Comparator.nullsLast(Comparator.reverseOrder())));

    return sortingOptions.get(portfolioFilterDto.getSortParameter());
}

And then I call the sort on my list like this

businessPartnerAssignmentDetails.sort(getComparator(portfolioFilterDto));

Upvotes: 1

Views: 118

Answers (4)

daniu
daniu

Reputation: 15008

You can wrap all this in an enum, like Sourin says, but you can enrich it with more functionality like actually comparing.

enum AssignmentFilter implements Comparator<BusinessPartnerAssignmentDetail> {
    fieldOfficeDescription(BusinessPartnerAssignmentDetail::getFieldOfficeDescription, Comparator.naturalOrder()),
    locationDescription(BusinessPartnerAssignmentDetail::getLocationDescription),
    segmentType(BusinessPartnerAssignmentDetail::getSegmentType),
    displayName(BusinessPartnerAssignmentDetail::getDisplayName);

    private Function<BusinessPartnerAssignmentDetail, Comparable<? super BusinessPartnerAssignmentDetail>> compareByKey;

    AssignmentFilter(Function<BusinessPartnerAssignmentDetail, Comparable<? super BusinessPartnerAssignmentDetail>> byKey, Comparator<BusinessPartnerAssignmentDetail> whenNull) {
        compareByKey = Comparator.comparing(byKey, Comparator.nullsLast(whenNull));
    }
    AssignmentFilter(Function<BusinessPartnerAssignmentDetail, Comparable<? super BusinessPartnerAssignmentDetail>> byKey) {
        this(byKey, Comparator.reverseOrder());
    }

    public int compare(BusinessPartnerAssignmentDetail bpad1, BusinessPartnerAssignmentDetail bpad1) {
        return comparator().compare(bpad1, bpad2);
    }
}

You can call this by businessPartnerAssignmentDetails.sort(AssingmentFilter.valueOf(portfolioFilterDto.getSortParameter())).

Whether this is better than just filling a map is your call.

Upvotes: 0

Sourin Ghosh
Sourin Ghosh

Reputation: 823

Try using an ENUM for Sorting options and use a switch case to get the particular Comparator :

DTO with Enum filter options

public class PortfolioFilterDto {
/*your existing code
 *
 *
 * */
PortfolioFilterDtoOptions sortParameter;
enum PortfolioFilterDtoOptions {
    fieldOfficeDescription, locationDescription, segmentType, displayName
 }

public PortfolioFilterDtoOptions getSortParameter() {
    return this.sortParameter;
 }
}

Your method to dynamically get filter option

private Comparator<? super BusinessPartnerAssignmentDetail> getComparator(PortfolioFilterDto portfolioFilterDto) {
    switch (PortfolioFilterDto.getSortParameter()) {
        case PortfolioFilterDtoOptions.fieldOfficeDescription:
            return Comparator.comparing(BusinessPartnerAssignmentDetail::getFieldOfficeDescription, Comparator.nullsLast(Comparator.naturalOrder()));
        case PortfolioFilterDtoOptions.locationDescription:
            return Comparator.comparing(BusinessPartnerAssignmentDetail::getLocationDescription, Comparator.nullsLast(Comparator.reverseOrder()));
        case PortfolioFilterDtoOptions.segmentType:
            return Comparator.comparing(BusinessPartnerAssignmentDetail::getSegmentType, Comparator.nullsLast(Comparator.reverseOrder()));
        case PortfolioFilterDtoOptions.displayName:
            return Comparator.comparing(BusinessPartnerAssignmentDetail::getDisplayName, Comparator.nullsLast(Comparator.reverseOrder()));
        default:
            // put default filter here
            return null;
    }
}

Upvotes: 0

Nikolas
Nikolas

Reputation: 44476

The only shortcut I see is to automatize the transformation of the String method representation to the Function<? super BusinessPartnerAssignmentDetail, U> using reflection.

Important: This solution might be useful in a huuuge number of getters and possible combinations. The getters must be formal parameters free (standard getters). I would prefer to stick with your current solution which is the way more readable and maintainable, which I consider as the priority.

Solution and its description:

  1. Extract Method from the String using Class::getMethod.
  2. Create Function<? super BusinessPartnerAssignmentDetail, U> from Method.
    • Use Method::invoke
    • The very same method invocation throws 2 exceptions, so the lambda expression creation should better be wrapped it into a separate method (silentInv) for sake of clarity.
  3. Return the correct comparator wither with natural or reverse order based on the method name compared to a list of the names (I recommend to make this list static).
static List<String> naturalOrderList = Arrays.asList("fieldOfficeDescription");

static <U extends Comparable<U>> Comparator<? super BusinessPartnerAssignmentDetail> 
    getComparator(PortfolioFilterDto p) throws NoSuchMethodException 
{
    /** (1) **/ 
    Method method = BusinessPartnerAssignmentDetail.class.getMethod(p.getSortParameter());

    /** (2) **/ 
    Function<? super BusinessPartnerAssignmentDetail, U> function = silentInv(method);

    /** (3) **/ 
    Comparator<U> order = methodsWithNaturalOrders.contains(method.getName()) 
        ? Comparator.naturalOrder() 
        : Comparator.reverseOrder();
    return Comparator.comparing(function, Comparator.nullsLast(order));
}

@SuppressWarnings("unchecked")
static <U extends Comparable<U>> Function<? super BusinessPartnerAssignmentDetail, U> 
    silentInv(Method method) 
{
    /** (2) The necessary try-catch wrapping, the exception should never be thrown **/ 
    return bpad -> {
        try {
            return (U) method.invoke(bpad);
        } catch (Exception e) {
            String message = "Invalid method name " + method.getName();
            throw new IllegalArgumentException(message , e);
        }
    };
}

Hint: Use shorter class names, if possible :)

Upvotes: 1

emrhzc
emrhzc

Reputation: 1480

creating a map of comparators each time the method is invoked is a bad idea.

instead you could return the required comparator in a switch case

    switch (portfolioFilterDto.getSortParameter()){
        case "fieldOfficeDescription" :
            return Comparator.comparing(BusinessPartnerAssignmentDetail::getFieldOfficeDescription, Comparator.nullsLast(Comparator.naturalOrder()));
        case "locationDescription" :
            return Comparator.comparing(BusinessPartnerAssignmentDetail::getLocationDescription, Comparator.nullsLast(Comparator.reverseOrder()));       
        case "segmentType" :
            return Comparator.comparing(BusinessPartnerAssignmentDetail::getSegmentType, Comparator.nullsLast(Comparator.reverseOrder()));
        case "displayName" :
            return Comparator.comparing(BusinessPartnerAssignmentDetail::getDisplayName, Comparator.nullsLast(Comparator.reverseOrder()));
        default :
            return null;
    }

or you can create the map inside the constructor as a global field, then you wouldn't initialize a map every time it's revoked.

Upvotes: 0

Related Questions