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!