Create custom filter predicate from Maps lambda
I want to create a dynamic predicate to filter out the list of employees based on the given filter map. I have this example of a filter like this :
Map<String, String> filters = new HashMap<String, String>();
filters.put("firstName", "John");
filters.put("country", "US");
//can add new filters in the future
I know the traditional way is to filter it like this :
employees.stream().filter(e -> e.getFirstName().equals("John") && e.getCountry().equals("US")).collect(Collectors.toList());
However I want to make the predicate in the filter method dynamic based on the filter maps that was pass. I tried experimenting but still no luck. What is the best way to do this?
If you need a solution without changing Employee
you can use reflection:
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Predicate;
public class PredicateBuilder {
private Predicate<Employee> predicate;
private boolean isError = false;
public PredicateBuilder(Map<String, String> filters ) {
buildPredicate(filters);
}
private void buildPredicate(Map<String, String> filters) {
predicate = null;
for(String propertyName : filters.keySet()){
Method method = methodByName(convertPropertyNameToGetterName(propertyName));
if(method == null ) {
continue;
}
String value = filters.get(propertyName);
Predicate<Employee> p = makePredicate(method, value);
if(p == null ) {
continue;
}
predicate = predicate == null ? p : predicate.and(p);
}
}
private String convertPropertyNameToGetterName(String propertyName) {
propertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
return "get"+propertyName;
}
private Predicate<Employee> makePredicate(Method method, String value){
isError = false;
Predicate<Employee> p = e -> {
try {
return method.invoke(e).equals(value);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
ex.printStackTrace();
isError = true;
return false;
}
};
return isError ? null : p;
}
private static Method methodByName(String name){
try {
return Employee.class.getDeclaredMethod(name);
} catch (NoSuchMethodException | SecurityException ex) {
ex.printStackTrace();
}
return null;
}
public Predicate<Employee> getPredicate() {
return predicate;
}
}
Usage example: Predicate<Employee> p = new PredicateBuilder(filters).getPredicate();
If you need a solution without changing Employee
and without using reflection, you'll end up with a rigid and not easy to maintain solution:
static Predicate<Employee> buildPredicate(Map<String, String> filters) {
Predicate<Employee> predicate = null;
for(String key : filters.keySet()){
Predicate<Employee> p = null;
switch (key) {
case "firstName":
p = e -> e.getFirstName().equals(filters.get(key));
break;
case "country":
p = e-> e.getCountry().equals(filters.get(key));
break;
}
if(p == null ) {
continue;
}
predicate = predicate == null ? p : predicate.and(p);
}
return predicate;
}