I have a templated repository holding a list of entities.
The type of the list is Collection<T>
, where T
is the type of each entity.
How can I implement the method public Iterable<T> sort(Sort sort)
on the repository to return the list of entities sorted by using the information from the argument sort
?
Bascially, an object of type Sort
is holding a list of type:
[("field1", ASC), ("field2", DESC), ("field3", ASC) ...]
, where the Strings
"field1", "field2", "field3" are attributes names of objects T
.
I have to implement the solution by using Java Streams.
So, when calling repository.sort(Sort.by("name").ascending().and(Sort.by("age").descending()))
the output should be a list of objects sorted ascending by name, and descending by age.
I am not allowed to use the existing similar Java Spring mechanism.
class Sort:
package Repository.Sorting;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Sort {
public static final Direction DEFAULT_DIRECTION = Direction.ASC;
private List<Order> orders;
public Sort(String property) {
this.orders = List.of(new Order(DEFAULT_DIRECTION, property));
}
public Sort(List<Order> orders) {
this.orders = orders;
}
public Sort(Direction direction, List<String> properties) {
if (!properties.isEmpty()) {
this.orders = properties
.stream()
.map(property -> new Order(direction, property))
.collect(Collectors.toList());
}
}
public static Sort by(String... properties) {
return new Sort(DEFAULT_DIRECTION, Arrays.asList(properties));
}
public Sort by(Direction direction, String... properties) {
return new Sort(direction, Arrays.asList(properties));
}
public Sort by(List<Order> orders) {
return new Sort(orders);
}
public Sort ascending() {
return this.by(
this.orders
.stream()
.map(order -> new Order(Direction.ASC, order.getProperty()))
.collect(Collectors.toList())
);
}
public Sort descending() {
return this.by(
this.orders
.stream()
.map(order -> new Order(Direction.DESC, order.getProperty()))
.collect(Collectors.toList())
);
}
public Sort and(Sort sort) {
List<Order> ownOrders = this.orders;
List<Order> otherOrders = sort.orders;
List<Order> allOrders = Stream
.concat(ownOrders.stream(), otherOrders.stream())
.collect(Collectors.toList());
return new Sort(allOrders);
}
public boolean unsorted() {
return this.orders.isEmpty();
}
public List<Order> getOrders() {
return this.orders;
}
}
class Direction:
package Repository.Sorting;
public enum Direction {
ASC,
DESC
}
class Order:
package Repository.Sorting;
public class Order {
private final Direction direction;
private final String property;
public Order(Direction direction, String property) {
this.direction = direction;
this.property = property;
}
public Direction getDirection() {
return this.direction;
}
public String getProperty() {
return this.property;
}
}
I tried creating a Comparator
object by using all the attributes names encapsulated by Sort
object, but I did not manage to solve the problem.
CodePudding user response:
I hope that I understood well your issue :)
You need to used Java Reflection API to do that.
I give you below an example of implementation.
First, the creation of the entity class :
abstract class MyEntityAbstract {
}
class MyEntity extends MyEntityAbstract {
private Integer field1;
private String field2;
public MyEntity(int field1, String field2) {
this.field1 = field1;
this.field2 = field2;
}
@Override
public String toString() {
return "MyEntity{"
"field1='" field1 '\''
", field2='" field2 '\''
'}';
}
}
Next, definition of an enum class which supply a Comparator associated to the type of your entity field (String (field2) / Integer (field1) in this example)
enum ComparatorType {
STRING(String.class) {
@Override
public Comparator getComparator() {
return (o1, o2) -> {
String val1 = (String) o1;
String val2 = (String) o2;
return val1.compareTo(val2);
};
}
},
INTEGER(Integer.class) {
@Override
public Comparator getComparator() {
return (o1, o2) -> {
Integer val1 = (Integer) o1;
Integer val2 = (Integer) o2;
return val1.compareTo(val2);
};
}
};
private final Class clazz;
ComparatorType(Class clazz) {
this.clazz = clazz;
}
public abstract Comparator getComparator();
public static ComparatorType from(Class<?> type) {
return Arrays.stream(values())
.filter(p -> p.clazz.equals(type))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Comparator type for '" type "' is not allow"));
}
}
Finally, the implemetation of the sort method in the Repository class :
class Repository<T extends MyEntityAbstract> {
private final List<T> myEntities = new ArrayList<>();
public void addEntity(T entity) {
this.myEntities.add(entity);
}
public Collection<T> sort(Sort sort) {
List<T> entitiesCopy = new ArrayList<>(myEntities);
entitiesCopy.sort((o1, o2) -> {
try {
int result = 0;
for (Order order : sort.getOrders()) {
// Recover the declared field with his name
Field field = o1.getClass().getDeclaredField(order.getProperty());
Field field2 = o2.getClass().getDeclaredField((order.getProperty()));
// Need to set accessible the field to recover his value
boolean isAccessible = field.isAccessible();
if (!isAccessible) {
field.setAccessible(true);
field2.setAccessible(true);
}
// Recover fields value
Object value1 = field.get(o1);
Object value2 = field2.get(o2);
// Reinit fields accessibility
field.setAccessible(isAccessible);
field2.setAccessible(isAccessible);
// Recover the comparator with the type of the field
Comparator comparatorFromClass = ComparatorType.from(field.getType()).getComparator();
// Compare values
if (order.getDirection() == Direction.ASC) {
result = comparatorFromClass.compare(value1, value2);
} else {
result = comparatorFromClass.compare(value2, value1);
}
// You need to break for loop if the compare for this order return 1 or -1 (order is already done, no need to order by the next fields in the list)
// If result == 0, the next order in your iteration will be apply
if (result != 0) {
break;
}
}
return result;
} catch (NoSuchFieldException | IllegalAccessException e) {
// Handle Exception
e.printStackTrace();
return 0;
}
});
return entitiesCopy;
}
}
For testing the code, I used this main method
public static void main(String[] args) {
Repository<MyEntity> repo = new Repository<>();
repo.addEntity(new MyEntity(110, "James"));
repo.addEntity(new MyEntity(163, "Robert"));
repo.addEntity(new MyEntity(31, "John"));
repo.addEntity(new MyEntity(256, "Michael"));
repo.addEntity(new MyEntity(59, "William"));
repo.addEntity(new MyEntity(59, "Robert"));
repo.addEntity(new MyEntity(59, "Bob"));
repo.addEntity(new MyEntity(59, "Thomas"));
repo.addEntity(new MyEntity(59, "Uber"));
repo.addEntity(new MyEntity(46, "David"));
repo.addEntity(new MyEntity(877, "Richard"));
repo.addEntity(new MyEntity(8, "Joseph"));
repo.addEntity(new MyEntity(39, "Thomas"));
System.out.println("Sort by 'field1' ASC");
repo.sort(Sort.by("field1")).forEach(System.out::println);
System.out.println("");
System.out.println("Sort by 'field1' DESC");
repo.sort(Sort.by(Direction.DESC, "field1")).forEach(System.out::println);
System.out.println("");
System.out.println("Sort by 'field2'");
repo.sort(Sort.by("field2")).forEach(System.out::println);
System.out.println("");
System.out.println("Sort by 'field2' DESC");
repo.sort(Sort.by(Direction.DESC, "field2")).forEach(System.out::println);
System.out.println("");
System.out.println("Sort by 'field1' ASC and 'field2' ASC");
repo.sort(Sort.by("field1").and(Sort.by("field2"))).forEach(System.out::println);
System.out.println("");
System.out.println("Sort by 'field2' ASC and 'field1' ASC");
repo.sort(Sort.by("field2").and(Sort.by("field1"))).forEach(System.out::println);
System.out.println("");
System.out.println("Sort by 'field1' DESC and 'field2' ASC");
repo.sort(Sort.by(Direction.DESC, "field1").and(Sort.by("field2"))).forEach(System.out::println);
System.out.println("");
System.out.println("Sort by 'field1' DESC and 'field2' DESC");
repo.sort(Sort.by(Direction.DESC, "field1").and(Sort.by(Direction.DESC, "field2"))).forEach(System.out::println);
}
The output :
Sort by 'field1' ASC
MyEntity{field1='8', field2='Joseph'}
MyEntity{field1='31', field2='John'}
MyEntity{field1='39', field2='Thomas'}
MyEntity{field1='46', field2='David'}
MyEntity{field1='59', field2='William'}
MyEntity{field1='59', field2='Robert'}
MyEntity{field1='59', field2='Bob'}
MyEntity{field1='59', field2='Thomas'}
MyEntity{field1='59', field2='Uber'}
MyEntity{field1='110', field2='James'}
MyEntity{field1='163', field2='Robert'}
MyEntity{field1='256', field2='Michael'}
MyEntity{field1='877', field2='Richard'}
Sort by 'field1' DESC
MyEntity{field1='877', field2='Richard'}
MyEntity{field1='256', field2='Michael'}
MyEntity{field1='163', field2='Robert'}
MyEntity{field1='110', field2='James'}
MyEntity{field1='59', field2='William'}
MyEntity{field1='59', field2='Robert'}
MyEntity{field1='59', field2='Bob'}
MyEntity{field1='59', field2='Thomas'}
MyEntity{field1='59', field2='Uber'}
MyEntity{field1='46', field2='David'}
MyEntity{field1='39', field2='Thomas'}
MyEntity{field1='31', field2='John'}
MyEntity{field1='8', field2='Joseph'}
Sort by 'field2'
MyEntity{field1='59', field2='Bob'}
MyEntity{field1='46', field2='David'}
MyEntity{field1='110', field2='James'}
MyEntity{field1='31', field2='John'}
MyEntity{field1='8', field2='Joseph'}
MyEntity{field1='256', field2='Michael'}
MyEntity{field1='877', field2='Richard'}
MyEntity{field1='163', field2='Robert'}
MyEntity{field1='59', field2='Robert'}
MyEntity{field1='59', field2='Thomas'}
MyEntity{field1='39', field2='Thomas'}
MyEntity{field1='59', field2='Uber'}
MyEntity{field1='59', field2='William'}
Sort by 'field2' DESC
MyEntity{field1='59', field2='William'}
MyEntity{field1='59', field2='Uber'}
MyEntity{field1='59', field2='Thomas'}
MyEntity{field1='39', field2='Thomas'}
MyEntity{field1='163', field2='Robert'}
MyEntity{field1='59', field2='Robert'}
MyEntity{field1='877', field2='Richard'}
MyEntity{field1='256', field2='Michael'}
MyEntity{field1='8', field2='Joseph'}
MyEntity{field1='31', field2='John'}
MyEntity{field1='110', field2='James'}
MyEntity{field1='46', field2='David'}
MyEntity{field1='59', field2='Bob'}
Sort by 'field1' ASC and 'field2' ASC
MyEntity{field1='8', field2='Joseph'}
MyEntity{field1='31', field2='John'}
MyEntity{field1='39', field2='Thomas'}
MyEntity{field1='46', field2='David'}
MyEntity{field1='59', field2='Bob'}
MyEntity{field1='59', field2='Robert'}
MyEntity{field1='59', field2='Thomas'}
MyEntity{field1='59', field2='Uber'}
MyEntity{field1='59', field2='William'}
MyEntity{field1='110', field2='James'}
MyEntity{field1='163', field2='Robert'}
MyEntity{field1='256', field2='Michael'}
MyEntity{field1='877', field2='Richard'}
Sort by 'field2' ASC and 'field1' ASC
MyEntity{field1='59', field2='Bob'}
MyEntity{field1='46', field2='David'}
MyEntity{field1='110', field2='James'}
MyEntity{field1='31', field2='John'}
MyEntity{field1='8', field2='Joseph'}
MyEntity{field1='256', field2='Michael'}
MyEntity{field1='877', field2='Richard'}
MyEntity{field1='59', field2='Robert'}
MyEntity{field1='163', field2='Robert'}
MyEntity{field1='39', field2='Thomas'}
MyEntity{field1='59', field2='Thomas'}
MyEntity{field1='59', field2='Uber'}
MyEntity{field1='59', field2='William'}
Sort by 'field1' DESC and 'field2' ASC
MyEntity{field1='877', field2='Richard'}
MyEntity{field1='256', field2='Michael'}
MyEntity{field1='163', field2='Robert'}
MyEntity{field1='110', field2='James'}
MyEntity{field1='59', field2='Bob'}
MyEntity{field1='59', field2='Robert'}
MyEntity{field1='59', field2='Thomas'}
MyEntity{field1='59', field2='Uber'}
MyEntity{field1='59', field2='William'}
MyEntity{field1='46', field2='David'}
MyEntity{field1='39', field2='Thomas'}
MyEntity{field1='31', field2='John'}
MyEntity{field1='8', field2='Joseph'}
Sort by 'field1' DESC and 'field2' DESC
MyEntity{field1='877', field2='Richard'}
MyEntity{field1='256', field2='Michael'}
MyEntity{field1='163', field2='Robert'}
MyEntity{field1='110', field2='James'}
MyEntity{field1='59', field2='William'}
MyEntity{field1='59', field2='Uber'}
MyEntity{field1='59', field2='Thomas'}
MyEntity{field1='59', field2='Robert'}
MyEntity{field1='59', field2='Bob'}
MyEntity{field1='46', field2='David'}
MyEntity{field1='39', field2='Thomas'}
MyEntity{field1='31', field2='John'}
MyEntity{field1='8', field2='Joseph'}
Hope that will help you !
And sorry for my bad english :) Trying my best !
CodePudding user response:
repository.sort(Sort.by("name").ascending().and(Sort.by("age").descending()))
Could use Collection<T>.sort(Comparator<T>)
.
repository.sort(Comparator.comparing(MyEntity::getName)
.thenComparing(
Comparator.comparingInt(MyEntity::getAge).reversed()));
(Assuming int age
.)
The complex syntax is unfortunately caused by doing descendant (reversed
) only for the age field.
When there is no need for a meta-level, like generating SQL, you probably would still be better of by the standard Comparator<MyEntity>
.
Otherwise there are also frameworks like JPA (eclipseLink, hibernate) with some expressive power.