1

I have a List of following objects:

public class OptionDetailResponse {
    private long id;

    private String flavor;
    private String size;
    private String status;
    private String barcode;
}

I want to search in a List of those objects based on all 4 fields (except id):

  • flavor (input from a combobox)
  • size (input from a combobox)
  • status (input from a combobox)
  • barcode (input from a textfield)

This is my UI with the 4 input fields:

screenshot of the UI with 4 input fields

What I tried

I tried to use Predicate<OptionDetailResponse> for searching:

Predicate<OptionDetailResponse> selectFlavor = e -> e.getParentName().equals(flavor);

Predicate<OptionDetailResponse> selectSize = e -> e.getName().equals(size);

Predicate<OptionDetailResponse> selectStatus = e -> e.getStatus().equals(status);

Predicate<OptionDetailResponse> inputBarcode = e -> e.getBarcode().contains(barcode);

List<OptionDetailResponse> list = responseList.stream().filter(
      selectFlavor.and(selectSize).and(selectStatus).and(inputBarcode))
      .collect(Collectors.<OptionDetailResponse>toList());

But the list returned only a correct result when selected a value for in all search-fields.

Questions

  1. How can I have all list when all field is empty using Predicate ?
  2. Do have other ways to search by multiple fields ?
Naman
  • 27,789
  • 26
  • 218
  • 353
traccy00
  • 49
  • 10
  • 2
    You can not know ahead of time if a predicate matches but you can check after the list is collected: `list = list.size() == 0 ? responseList: list;`. Also, you should return a defensive copy: `List.copyOf(responseList)` if that is relevant to your case. – Aniket Sahrawat Apr 27 '21 at 14:43
  • 2. a useful resource for composite predicates https://stackoverflow.com/questions/24553761/how-to-apply-multiple-predicates-to-a-java-util-stream – Naman Apr 27 '21 at 16:23

4 Answers4

1

I think you can check on nullability or on specific value which shouldn't be checked inside each of your predicates depending on value you have in unselected field. I think it can look like this:

Predicate<OptionDetailResponse> selectFlavor = e -> flavor == null || e.getParentName().equals(flavor);

or

Predicate<OptionDetailResponse> selectFlavor = e -> flavor.equals("your unselected flavor value") || e.getParentName().equals(flavor);

.. and same for other predicates.

Naman
  • 27,789
  • 26
  • 218
  • 353
Sergey Vasnev
  • 783
  • 5
  • 18
0

Bear in mind that when you use a Predicate in a filter method, the result will be the list of elements which match the "test" operation of the supplied predicate.
What you have done is to create a chain of Predicates in logical AND. This means that the result of the filter will be the list of the elements which match ALL given predicates.
if you need for a different result, you can create your chain by applying by using the specific Predicate functions, and thus realize eventually a more complex condition.
Other than and(Predicate<> target), for example you have the following method:

  • or(Predicate<> target): short-circuiting logical OR between two Predicate
  • negate(): logical negation of the current instance of Predicate
  • not(Predicate<> target): returns a predicate that is the negation of the supplied predicate
Marco Tizzano
  • 1,726
  • 1
  • 11
  • 18
0

Probably you want to use following filter on each respective field:

  • if a search-parameter not supplied by UI (or left empty), then don't apply predicate: means predicate matches all regardless of object's field value
  • if a search-parameter is supplied by UI (or not empty), then apply the predicate

That said you could use a filter combined from all filled input-fields in stream:

// renamed your stream-result variable to indicate that it was filtered
List<OptionDetailResponse> filteredResult = responseList.stream()
      .filter( buildPredicateFromInputFields(flavor, size, status, barcode) )
      .collect(Collectors.toList());

where the predicate passed as argument to filter is combined from the 4 fields:

// you could name the method more specific: matchesAllNonEmptyInputs
Predicate<OptionDetailResponse> buildPredicateFromInputFields(
    String flavor,
    String size,
    String status,
    String barcode
) {
  // build a set of field-matchers (predicate) based on given input fields
  // null or empty (or blank) fields are excluded from the set
  var givenFieldPredicates = new ArrayList<Predicate<OptionDetailResponse>>(4); // max 4 entries 

  if (flavor != null && !flavor.isBlank()) {
      givenFieldPredicates.add(obj -> flavor.equals(obj.flavor))
  }
 
  if (size != null && !size.isBlank()) {
      givenFieldPredicates.add(obj -> size.equals(obj.size))
  }

  if (status != null && !status.isBlank()) {
      givenFieldPredicates.add(obj -> status.equals(obj.size))
  }

  // contained (partial match allowed)
  if (barcode != null && !barcode.isBlank()) {
      // will throw NullPointerException if object has null barcode!
      givenFieldPredicates.add(obj -> obj.barcode.contains(barcode))
  }

  // combined them using AND: each field predicate must match
  return givenFieldPredicates.stream().reduce(x -> true, Predicate::and);
}

See also:

hc_dev
  • 8,389
  • 1
  • 26
  • 38
0

We could use Function, BiFunction and method reference and a pojo to hold the way to filter a field list to build something like

    @Value
    public static class Filter<T> {
        private Function<OptionDetailResponse, T> getter;
        private BiFunction<T, T, Boolean> filter;

        public Predicate<OptionDetailResponse> toPredicate(OptionDetailResponse criteria) {
            return o -> filter.apply(getter.apply(o), getter.apply(criteria));
        }
    }

    public static List<Filter<?>> filters() {
        List<Filter<?>> filterList = new ArrayList<>();
        filterList.add(new Filter<>(OptionDetailResponse::getFlavor, Object::equals));
        filterList.add(new Filter<>(OptionDetailResponse::getSize, Object::equals));
        filterList.add(new Filter<>(OptionDetailResponse::getStatus, Object::equals));
        filterList.add(new Filter<>(OptionDetailResponse::getBarcode, String::contains));
        return filterList;
    }

    public static final List<Filter<?>> FILTERS = filters();

    public Predicate<OptionDetailResponse> buildPredicate(OptionDetailResponse searchCriteria) {

        return FILTERS
                .stream()
                .filter(f -> f.getGetter().apply(searchCriteria) != null)
                .map(f -> f.toPredicate(searchCriteria))
                .reduce(o -> true, Predicate::and);
    }


    public List<OptionDetailResponse> search(List<OptionDetailResponse> responseList,
                                             OptionDetailResponse searchCriteria) {

        return responseList.stream()
                .filter(buildPredicate(searchCriteria))
                .collect(Collectors.toList());
    }