How to implement dynamic run time query to filter a Java stream of objects

39 views Asked by At

Say I have a List of objects of the following type:

public class Employee {
    private String emplId;
    private Name name;
    private String dept;
    private String city;
}

And I have the following list:

List<Employee> empLst = Arrays.asList(
        new Employee("1", "henry", "10", "Boston"),
        new Employee("2", "Foster", "10", "san jose"),
        new Employee("3", "Rick", "10", "sfo"),
        new Employee("4", "Ban", "20", "Boston"),
        new Employee("5", "Zen", "20", "Ale"),
        new Employee("6", "Ken", "30", "sfo")
);

How can I implement a search which accepts an employee object and filters the list that matches query object values?

GetEmplList(new Employee().setCity("Boston")); // returns both #1 and #4 employees
GetEmplList(new Employee().setCity("Boston").setDept("20")); // returns only #4 employee
GetEmplList(new Employee().setName("Ken")); // returns only #6 employees

I don't want something like the following compile-time filter:

empLst.stream()
     .filter(e -> e.getCity().equalsIgnoreCase("Boston"))
     .forEach(e -> System.out.println(e.getName()));
1

There are 1 answers

0
Chaosfire On

You need a Predicate matching an element with a provided 'example'. Properties being null in the example should be considered matching.

import java.util.function.Predicate;

public class ExampleBasedFilter implements Predicate<Employee> {

  private final Employee example;

  public ExampleBasedFilter(Employee example) {
    this.example = example;
  }

  @Override
  public boolean test(Employee employee) {
    return match(example.getEmplId(), employee.getEmplId())
            && match(example.getCity(), employee.getCity())
            && match(example.getName(), employee.getName())
            && match(example.getDept(), employee.getDept());
  }

  private static boolean match(Object exampleProperty, Object propertyToTest) {
    if (exampleProperty == null) {
      return true;
    }
    return exampleProperty.equals(propertyToTest);
  }
}

Usage:

Employee example = new Employee(null, null, "20", "Boston");
List<Employee> filteredResult = empLst.stream()
        .filter(new MatchByExample(example))
        .toList();
System.out.println(filteredResult);
//prints the employee with id 4

Or a more generic implementation of Predicate using reflection, which can work for any class:

import java.lang.reflect.Field;
import java.util.function.Predicate;

public class GenericExampleBasedFilter<T> implements Predicate<T> {

  private final T example;

  public GenericExampleBasedFilter(T example) {
    this.example = example;
  }

  @Override
  public boolean test(T t) {
    for (Field field : example.getClass().getDeclaredFields()) {
      field.setAccessible(true);
      try {
        Object exampleValue = field.get(example);
        Object testValue = field.get(t);
        if (!match(exampleValue, testValue)) {
          return false;
        }
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
      } finally {
        field.setAccessible(false);
      }
    }
    return true;
  }

  private static boolean match(Object exampleProperty, Object propertyToTest) {
    if (exampleProperty == null) {
      return true;
    }
    return exampleProperty.equals(propertyToTest);
  }
}