Bremersee Comparator

This project contains a builder for comparing and sorting objects.

The comparator can compare any kind of objects which have the same attributes or the same ‘getters’. It uses reflection to get the values of these attributes or ‘getters’. The values may be a simple type like java.lang.String or a complex type which implements java.lang.Comparable.

Usage

Given is for example a tree that is represented by these classes:

The common Node class:

import java.util.Date;

abstract class Node {

  private Date createdAt;
  private String name;
  // getter and setter
}

The Branch class:

import java.util.List;
import java.util.ArrayList;

class Branch extends Node {

  private List<Node> children = new ArrayList<>();
  // getter
}

The Leaf class:

class Leaf extends Node {

  private String value;
  // getter and setter
}

You want to sort a list of nodes by name. And if the names are equal by created date:

import org.bremersee.comparator.*;
import java.util.List;
import java.util.ArrayList;

class Example {

  public static void main(String[] args) {
    List<Node> list = new ArrayList<>();
    // add nodes
    list.sort(ComparatorBuilder.newInstance()
        .add("name", true, true, false)        // fieldName, asc, ignoreCase, nullIsFirst
        .add("createdAt", false, true, false)  // fieldName, asc, ignoreCase, nullIsFirst
        .build());
  }
}

That's all. All nodes in the list are sorted by name and date. But what happens, if you want to sort them by type (first the branches and then the leafs) and then by name and date? There is no field that stores the type. Then you can do this:

import org.bremersee.comparator.*;
import java.util.List;
import java.util.ArrayList;

class Example {

  public static void main(String[] args) {
    List<Node> list = new ArrayList<>();
    // add nodes
    list.sort(ComparatorBuilder.newInstance()
        .add((o1, o2) ->
            (o1 instanceof Branch && o2 instanceof Branch)
                || (o1 instanceof Leaf && o2 instanceof Leaf) ? 0 : o1 instanceof Branch ? -1 : 1)
        .add("name", true, true, false)        // fieldName, asc, ignoreCase, nullIsFirst
        .add("createdAt", false, true, false)  // fieldName, asc, ignoreCase, nullIsFirst
        .build());
  }
}

Now you have a list, that contains the branches first, sorted by name and date, and then the leafs.

Comparator arguments (class SortOrder)

There are four attributes which define the comparison.

Attribute Description Default
field The field name (or method name) of the object. It can be a path. null
The segments are separated by a dot (.): field0.field1.field2
It can be null. Then the object itself must be comparable.
asc or desc Defines ascending or descending ordering. asc
ignoreCase Makes a case ignoring comparison (only for strings). true
nullIsFirst Defines the ordering if one of the values is null. false

The field name can also be a path to a value, if you have complex objects, for example room.numer, person.lastName or person.firstName, if your classes look like this:

class Employee {

  private Person person;
  private Room room;
  // getter and setter
}

with Person

class Person {

  private String lastName;
  private String firstName;
  // getter and setter
}

and Room

class Room {

  private int number;
  // getter and setter
}

The sorting of employees:

import org.bremersee.comparator.*;
import org.bremersee.comparator.model.*;
import java.util.List;
import java.util.ArrayList;

class Example {

  public static void main(String[] args) {
    List<Employee> list = new ArrayList<>();
    // add employees
    list.sort(ComparatorBuilder.newInstance()
        .addAll(List.of(
            new SortOrder("room.number", true, true, false),
            new SortOrder("person.lastName", true, true, false),
            new SortOrder("person.firstName", true, true, false)
        ))
        .build());
  }
}

Spring Framework Support

REST support

The SortOrder has a string representation, that can be used to pass the sort order as a query parameter into a RestController.

The syntax is:

fieldName,asc,ignoreCase,nullIsFirst

Example:

room.number,asc,true,false

Or:

createdAt,desc,true,false

The rest controller may look like this (with OpenApi annotations):

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import org.bremersee.comparator.model.SortOrder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestRestController {

  @Operation(
      summary = "Get something that can be sorted.",
      operationId = "getSomethingSorted",
      tags = {"test-controller"}
  )
  @GetMapping(path = "/")
  public ResponseEntity<List<Employee>> getSomethingSorted(
      @Parameter(array = @ArraySchema(schema = @Schema(type = "string")))
      @RequestParam(name = "sort", required = false) List<SortOrder> sort) {

    List<Empployy> list = service.findEmployees();
    list.sort(ComparatorBuilder.newInstance()
        .addAll(sort)
        .build());

    return ResponseEntity.ok(list);
  }
}

The url may look like this:

http://localhost:8080?sort=room.number,asc&sort=person.lastName,asc,true

The parsing of the string is done by providing the SortOrderConverter as a Spring bean:

import org.bremersee.comparator.spring.converter.SortOrderConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SortOrderConverterConfiguration {

  @Bean
  public SortOrderConverter sortOrderConverter() {
    return new SortOrderConverter();
  }

}

Spring Sort Mapper

The Spring Common Data project contains a class for sorting, too. The class SortMapper contains methods to transform the sort orders of this library into the objects of the spring framework.

To use the Spring Framework Support you have to add the following dependency to your project:

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-commons</artifactId>
  <version>{your-spring-data-commons-version}</version>
</dependency>

XML Schema

The XML schema of the model is available here.