Building a dynamic predicate from a custom map of conditions – Functional style programming – extending API
188. Building a dynamic predicate from a custom map of conditions
Let’s consider the Car model and a List<Car> denoted as cars:
public class Car {
private final String brand;
private final String fuel;
private final int horsepower;
…
}
And, let’s assume that we receive a Map of conditions of type field : value that should be used to build a dynamic Predicate. An example of such a Map is listed here:
Map<String, String> filtersMap = Map.of(
“brand”, “Chevrolet”,
“fuel”, “diesel”
);
As you can see, we have a Map<String, String>, so we are interested in an equals() comparison. This is useful to start our development via the following Java enum (we follow the logic from Problem 187):
enum PredicateBuilder {
EQUALS(String::equals);
…
Of course, we can add more operators such as startsWith(), endsWith(), contains(), and so on. Next, based on the experience gained in Problems 186 and 187, we need to add a BiPredicate constructor, the toPredicate() method, and the Java Reflection code for fetching the getters corresponding to the given fields (here, brand and fuel):
private final BiPredicate<String, String> predicate;
private PredicateBuilder(
BiPredicate<String, String> predicate) {
this.predicate = predicate;
}
public <T> Predicate<T> toPredicate(
Function<T, String> getter, String u) {
return obj -> this.predicate.test(getter.apply(obj), u);
}
public static <T> Function<T, String>
getFieldByName(Class<T> cls, String field) {
return object -> {
try {
Field f = cls.getDeclaredField(field);
f.setAccessible(true);
return (String) f.get(object);
} catch (
IllegalAccessException | IllegalArgumentException
| NoSuchFieldException | SecurityException e) {
throw new RuntimeException(e);
}
};
}
}
Next, we have to define a predicate for each map entry and chain them via the short-circuiting AND operator. This can be done in a loop as follows:
Predicate<Car> filterPredicate = t -> true;
for(String key : filtersMap.keySet()){
filterPredicate
= filterPredicate.and(PredicateBuilder.EQUALS
.toPredicate(PredicateBuilder.getFieldByName(
Car.class, key), filtersMap.get(key)));
}
Finally, we can use the resulting predicate to filter the cars:
cars.stream()
.filter(filterPredicate)
.forEach(System.out::println);
Done!
Leave a Reply