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;
 }