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:
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
How can I have all list when all field is empty using Predicate ?
Do have other ways to search by multiple fields ?
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.
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
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:
Baeldung's Java Tutorial: Java 8 Predicate Chain, section "6. Combining a Collection of Predicates"
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());
}
Related
I have a list of sources from a JSON file stored in S3. I need to provide search filters on few fields of the list. I have created a Builder class with all the needed fields on which the data can be filtered.
Now I want to use Java streams and apply the filters on the data based on the parameters the users will provide. Users can provide all the 6 parameters or only pass 2 or 3 parameters based on their need. For example user provides only 2 parameters out of 6 I should be able to filter the data based on the 2 parameters given and ignore the 4 parameters where the value is null.
public class ListOfFields {
private String country;
#JsonProperty("product")
private String product;
private String transactionId;
private String sourceBy;
private String category;
private String specialTag;
private String organicTag;
private String successTag;
private String speedTag;
private String foodTag;
private String costPrice;
private String fruitType;
private String fruitVendorId;
}
The below is the builder class
public class ProductClass {
#Builder.Default
private String country = 'UnitedStates';
private String sourceBy;
private Boolean specialTag;
private String category;
private Boolean priceTag;
private String fruitType;
}
The below method where user can make a call to get the product
public String getProduct(ProductClass productQuery) {
List<ListOfFields> result = listOfFields // (the declaration is made before in the class as private List<ListOfFields> listOfFields;)
.stream().filter(productQuery::match)
.collect(Collectors.toList());
How do I come up with dynamic predicate as the match function above? I should be able to filter on the parameters provided by the user be it 1 or 2 or 3 or 4 ... and not using the parameters where the user did not pass any value in the filter criteria.
Considering the ProductClass as the input and null values for the absence of the keys. You can define the list of Predicates possible using a method that takes in the actual query parameter.
private static List<Predicate<ListOfFields>> predicates(ProductClass product) {
return List.of(
lof -> product.priceTag != null && product.priceTag,
lof -> product.specialTag != null && product.specialTag,
lof -> product.country != null && product.country.equals(lof.country),
lof -> product.sourceBy != null && product.sourceBy.equals(lof.sourceBy),
lof -> product.category != null && product.category.equals(lof.category),
lof -> product.fruitType != null && product.fruitType.equals(lof.fruitType)
);
}
Upon reaching this, the list could be reduced to represent a single Predicate such as:
private static Predicate<ListOfFields> matchAll(List<Predicate<ListOfFields>> queries) {
return queries.stream()
.reduce(Predicate::and)
.orElse(p -> true);
}
Now one can easily make use of this predicate while streaming the objects of type ListOfFields --
List<ListOfFields> result = listOfFields.stream()
.filter(matchAll(predicates(productQuery)))
.collect(Collectors.toList());
You can use regular if statements for each parameter and apply filter which returns Stream.
Stream< ListOfFields> stream = listOfFields.stream();
if(first parameter exists) {
stream = stream.filter(predicate for first parameter);
}
if(second parameter exists) {
stream = stream.filter(predicate for second parameter);
}
...
List< ListOfFields> result = stream.collect(Collectors.toList());
I have a list of:
String[] properties = {"prop1","prop2","prop3"};
List<CustomObject> listOfObjects; // contains objects and each object contains a property of `properties` array
class CustomObject{
String prop1;
String prop2;
String prop3;
}
I want to check every properties value to every listOfObjects objects properties and check if the value is empty. I just want to check that none of the objects have empty properties.
I know there's stream() but I'm not sure how to use it correctly.
listOfObjects.stream().anyMatch(x -> x.get(/*What do i put in here?*/) == "");
Update: here's what would basically work without stream()
for(String prop: properties) {
for(CustomObject pdo: listOfObjects) {
if(pdo.get(prop) == "") {
System.out.println("Some of the required fields are empty");
}
}
}
Just for the sake of using Streams and anyMatch you can use this operation to check if any of the properties are blank. This is just to answer your question specifically, my suggestion is to take some time and improve this as there is a lot of room.
listOfObject.stream()
.anyMatch(obj -> Arrays.stream(obj)
.anyMatch(p -> !(obj.get(p)!= null && !obj.get(p).isEmpty())));
You can just use a list of Predicates. Each of the following predicates tests one of the properties for equality against "".
List<Predicate<CustomObject>> propertiesCheckers =
Arrays.asList(pdo -> "".equals(pdo.get("prop1")),
pdo -> "".equals(pdo.get("prop2")),
pdo -> "".equals(pdo.get("prop3")));
And this stream checks each element against the predicate, returning true when the first empty value is found:
boolean anyEmpty = listOfObjects.stream()
.flatMap(ob -> propertiesCheckers.stream().map(pred -> pred.test(ob)))
.anyMatch(b -> b); //returns true if any "".equals returned true
if(anyEmpty) System.out.println("Some of the required fields are empty");
I need to validate mandatory fields in my class
For example, 9 fields must not be null.
I need to check if they are all null but I am using multiple if statements for this now as below:
StringBuilder mandatoryExcessFields = new StringBuilder(MANDATORY_EXCESS_FIELDS.length);
if(Objects.isNull(excess.getAsOfDate())){
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[0]);
}
if(StringUtils.isEmpty(excess.getStatus())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[1]);
}
if(Objects.isNull(excess.getLimit())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[2]);
}
if(!Objects.isNull(excess.getLimit()) && Objects.isNull(excess.getLimit().getId())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[3]);
}
if(!Objects.isNull(excess.getLimit()) && Objects.isNull(excess.getLimit().getAsOfDate())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[4]);
}
if(Objects.isNull(excess.getExposure())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[5]);
}
if(!Objects.isNull(excess.getExposure()) && Objects.isNull(excess.getExposure().getCoordinates())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[6]);
}
if(!Objects.isNull(excess.getExposure()) && Objects.isNull(excess.getExposure().getValue())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[7]);
}
if(StringUtils.isEmpty(excess.getLimitValue())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[8]);
}
Do we have a better approach to reduce this boilerplate code or any design pattern or any new feature from Java-8 which I can leverage?
All the Object.isNull might be replaced with Optional object and its methods. Let's take example the line:
if (!Objects.isNull(excess.getLimit()) && Objects.isNull(excess.getLimit().getId())) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[3]);
}
Would be simplified to (and squeezed on 1 line remains readable):
Optional.ofNullable(excess.getLimit()) // check the Limit
.map(limit -> limit.getId()) // if not null, getId
.ifPresent(i -> builder.append(MANDATORY_EXCESS_FIELDS[3])); // Append if present
And for the String.isEmpty(s) check, you have to create Optional in this way:
Optional.ofNullable(excess.getStatus()).filter(s -> !StringUtils.isEmpty(s))
A short way would be to pass those Optional object into the map and use the index to iterate through them and perform an action. int count is a number of checkings:
Map<Integer, Optional<?>> map = new HashMap<>();
map.put(...);
map.put(1, Optional.ofNullable(excess.getStatus()).filter(s -> !StringUtils.isEmpty(s)));
map.put(...);
map.put(3, Optional.ofNullable(excess.getLimit()).map(limit -> limit.getId()));
map.put(...);
for (int index=0; index<count; index++) {
map.get(index).ifPresent(any -> mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[index]));
}
And the for-cycle might be simplified as well:
IntStream.range(0, count).forEach(index ->
map.get(index)
.ifPresent(any -> mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[index])));
Basically, there are two ways here:
As suggested by the comment, NonNull as offered by Project Lombok for example
Java bean validation
I would heavily recommend to look into bean validation:
Define your classes that carry information as beans. And then use the wide range of annotations to mark the corresponding fields. And then use some existing framework to do the validation for you. You can even define your own annotations there, that run your own code.
You can use javax.validator and hibernate.validator with #NotNull annotation on each field (or whichever field you want) on your excess POJO class. This combination provides an extensive pattern checking as well.
By this you don't have to do all the if checks explicitly. You can get ride of not only null checks but also pattern matching checks which can get scattered all over your code.
Basically the initialisation and assignments should not set any field to null.
If this is unopportune (a field being really logically optional), the field should probably be an Optional<...>, assigned with an Optional.ofNullable(...). This ensures that at usage the field is safely processed, but causes editing work of course.
Seeing the code now, here it seems that there is no easy refactoring.
The code could be refactored; somewhere a mapping of features is missing.
Predicate<Excess>[] parts = {
exc -> Objects.isNull(exc.getAsOfDate()),
exc -> StringUtils.isEmpty(exc.getStatus()),
...
};
for (int i = 0; i < parts.length; ++i) {
if (parts[i].test(excess)) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[i]);
}
}
Or such.
As easy refactoring you could introduce two helper methods :
private String createErrorMsgIfObjectNull(Object o, String errorMessage) {
return Objects.isNull(o) ? errorMessage : "";
}
private String createErrorMsgIfStringEmpty(String s, String errorMessage) {
return StringUtils.isEmpty(s) ? errorMessage : "";
}
And use them in this way :
StringBuilder mandatoryExcessFields = new StringBuilder(MANDATORY_EXCESS_FIELDS.length);
mandatoryExcessFields.append(createErrorMsgIfObjectNull(excess.getAsOfDate(), MANDATORY_EXCESS_FIELDS[0]))
.append(createErrorMsgIfStringEmpty(excess.getStatus(), MANDATORY_EXCESS_FIELDS[1]))
.append(createErrorMsgIfObjectNull(excess.getLimit(), MANDATORY_EXCESS_FIELDS[2]))
// ...
By checking the type of the object to test you could still go further. You would have a single helper method that will apply the processing according to the argument type :
private String createErrorMsgIfNullOrEmptyString(Object o, String errorMessage) {
if (o instanceof String) {
return StringUtils.isEmpty((String)o) ? errorMessage : "";
}
return Objects.isNull(o) ? errorMessage : "";
}
A Java 8 stream way would inline the helper in a filter and map() operations and would collect the String result :
List<SimpleImmutableEntry<Object, String>> objectAndErrorMessageList = new ArrayList<>();
int i = 0;
objectAndErrorMessageList.add(new SimpleImmutableEntry<>(excess.getAsOfDate(), MANDATORY_EXCESS_FIELDS[i++]));
objectAndErrorMessageList.add(new SimpleImmutableEntry<>(excess.getStatus(), MANDATORY_EXCESS_FIELDS[i++]));
// and so for
String globalErrorMsg =
objectAndErrorMessageList.stream()
.filter(e -> {
Object objectToValid = e.getKey();
if (objectToValid == null) {
return true;
}
if (objectToValid instanceof String && StringUtils.isEmpty(objectToValid)) {
return true;
}
return false;
})
.map(SimpleImmutableEntry::getValue)
.collect(Collectors.joining(""));
Other solution would be like this: same as #Nikolas answer.
Map<Integer, Predicate<Excess>> map = new HashMap<>();
Predicate<Excess> checkStatus = excess -> excess.getStatus().isEmpty();
Predicate<Excess> checkLimit = excess -> Objects.isNull(excess.getLimit());
Predicate<Excess> checkLimitId = excess -> Objects.isNull(excess.getLimit().getId());
Predicate<Excess> checkLimitAndId = checkLimit.and(checkLimitId);
// other predicates
map.put(1,checkStatus);
map.put(2,checkLimit);
map.put(3,checkLimitAndId);
// put other predicates ...
for (Map.Entry<Integer, Predicate<Excess>> m : map.entrySet()) {
if (m.getValue().test(excess)) {
mandatoryExcessFields.append(MANDATORY_EXCESS_FIELDS[m.getKey()]);
}
}
A little bit complicated, but I have a good solution because it's generic and can be used with any objects:
Excess excess = new Excess(new Limit());
Checker<Excess, Excess> checker = new Checker<>(
identity(),
List.of(
new CheckerValue<>("excess date is null", Excess::getAsOfDate),
new CheckerValue<>("limit is null", Excess::getLimit)
),
List.of(new Checker<>(Excess::getLimit, List.of(new CheckerValue<>("limit id is null", Limit::getId))))
);
System.out.println(checker.validate(excess));
This code will print:
excess date is null
limit id is null
The first class Checker contains:
sourceFunction - for getting the object
values - for checking each field from object obtained from sourceFunction
children - a list of Checker
class Checker<S, T> {
Function<S, T> sourceFunction;
List<CheckerValue<T>> values;
List<Checker<T, ?>> children = emptyList();
/*All args constructor; 2 args constructor*/
public String validate(S object) {
T value = sourceFunction.apply(object);
if(value != null) {
String valueString = values.stream().map(v -> v.validate(value)).filter(Optional::isPresent).map(Optional::get).collect(joining("\n"));
valueString += "\n\t";
valueString += children.stream().map(c -> c.validate(value)).collect(Collectors.joining("\n"));
return valueString;
}
return "";
}
}
and CheckerValue class:
class CheckerValue<T> {
String validationString;
Function<T, Object> fun;
/*all args constructor*/
public Optional<String> validate(T object) {
return fun.apply(object) != null ? Optional.empty() : Optional.of(validationString);
}
}
I currently have a multiple layer structure data that is like this:
Industry class has a private field Set<Company> that can be null.
Company class has a private field Set<Division> that can be null.
Division class has a private field Set<Group> that can be null.
Group class has a private field groupName that can be null and is
retrievable with a getter (getGroupName()).
I am trying to stream an instance of Industry all way down to the Group layer and concatenate all the groupName's into one String with "/" in between.
If the this instance of Industry doesn't contain any groupName, return the string "null".
Based on my limited knowledge of Java 8, I am thinking of coding like this:
industry.stream()
.flatmap(industry -> industry.getCompanies().stream())
.filter(Objects::nonNull)
.flatmap(company -> company.getDivisions().stream())
.filter(Objects::nonNull)
.flatmap(division -> division.getGroups().stream())
.map(group -> group.getGroupName)
.collect(Collectors.joining("/")));
This code seems to flawed in someway. Also, I am not sure where to add the statement that if Industry cannot retrieve any groupName, rather than concatenate all groupName into one string simply return a string "null".
What is the proper way to use Java 8 stream in my situation?
Thanks.
Collectors.joining(…) is based on the class StringJoiner. It offers its delimiter, prefix, and suffix features, but unfortunately not the ability to provide the empty value.
To add that feature, we’ll have to re-implement Collectors.joining, which thankfully is not so hard when using StringJoiner.
Change the last line of your stream operation
.collect(Collectors.joining("/"));
to
.filter(Objects::nonNull) // elide all null elements
.collect(()->new StringJoiner("/", "", "").setEmptyValue("null"), // use "null" when empty
StringJoiner::add, StringJoiner::merge).toString();
I understood your question as pretty much anything can be null. In this case you could create your own function to deal with this. I made one as such:
/**
* Creates a stream function for the provided collection function which ignores all null values.
* Will filter out null values passed into the collection function and null values from the resulting stream
* #param collectionFn
* #param <T>
* #param <R>
* #return
*/
public static <T, R> Function<T, Stream<R>> nullSafeMapper(Function<T, Collection<R>> collectionFn) {
return (v) -> Optional.ofNullable(v)
.map(collectionFn)
.map(Collection::stream)
.orElse(Stream.empty())
.filter(Objects::nonNull);
}
Basically its completely null safe, filtering out anything which is null in the input and output. and could be used as such:
industries.stream()
.flatMap(SO46101593.nullSafeMapper(Industry::getCompanies))
.flatMap(SO46101593.nullSafeMapper(Company::getDivisions))
.flatMap(SO46101593.nullSafeMapper(Division::getGroups))
.map(group -> group.getGroupName())
.filter(Objects::nonNull) // filter out null group names
.collect(Collectors.joining("/"));
You could also take that logic and push it down directly into your expression but since it has to be repeated 3 times it gets a bit... verbose and repetitive
Here is an example with null checks:
String s = industries.stream()
.filter( i -> i.getCompanies() != null ).flatMap( i -> i.getCompanies().stream() )
.filter( c -> c != null && c.getDivisions() != null ).flatMap( c -> c.getDivisions().stream() )
.filter( d -> d != null && d.getGroups() != null ).flatMap( d -> d.getGroups().stream() )
.filter( g -> g != null && g.getGroupName() != null ).map( g -> g.getGroupName() )
.collect( Collectors.joining("/") );
You can replace Collectors.joining("/") with Holger's example.
This should do it:
Stream.of(industry)
.map(Industry::getCompanies).filter(Objects::nonNull)
.flatMap(Set::stream)
.map(Company::getDivisions).filter(Objects::nonNull)
.flatMap(Set::stream)
.map(Division::getGroups).filter(Objects::nonNull)
.flatMap(Set::stream)
.map(Group::getGroupName).filter(Objects::nonNull)
.collect(Collectors.collectingAndThen(Collectors.joining("/"),
names -> names.isEmpty() ? "null" : names));
I'm assuming industry.stream() is incorrect, since you say you're working from "an instance of Industry". Instead, I make a Stream<Industry> with one element.
You need to do the null checks on the sets before you try to call stream on them. You're checking whether stream returns null, which is too late.
The final transform from empty result to "null" falls under the concept of the "finisher" function in Collector. Collectors.joining doesn't let you specify a finisher directly, but you can use Collectors.collectingAndThen to add a finisher to any existing Collector.
I have an entity with 10 fields:
Class Details{
String item;
String name;
String type;
String origin;
String color;
String quality;
String country;
String quantity;
Boolean availability;
String price;
}
I have a restful endpoint that serves a List. I want the user to be able to provide search filters for each field.
Currently I have QueryParam for each field. Then I filter by using java8 stream:
List<Detail> details;
details.stream().filter(detail-> detail.getItem()==item).filter(detail-> detail.getName()==name).....collect(Collectors.toList());
If I have 50 other classes with multiple fields that I want to filter, Is there a way of generalizing this?
You can compose such predicates with .and() and .or(), allowing you to define a single aggregate predicate that applies all the checks you would like, rather than trying to chain n .filter() calls. This enables arbitrarily complex predicates that can be constructed at runtime.
// Note that you shouldn't normally use == on objects
Predicate<Detail> itemPredicate = d-> item.equals(d.getItem());
Predicate<Detail> namePredicate = d-> name.equals(d.getName());
details.stream()
.filter(itemPredicate.and(namePredicate))
.collect(Collectors.toList());
If you want to avoid reflection how about something like this?
static enum DetailQueryParams {
ITEM("item", d -> d.item),
NAME("name", d -> d.name),
TYPE("type", d -> d.type),
ORIGIN("origin", d -> d.origin),
COLOR("color", d -> d.color),
QUALITY("quality", d -> d.quality),
COUNTRY("country", d -> d.country),
QUANTITY("quantity", d -> d.quantity),
AVAILABILITY("availability", d -> d.availability),
PRICE("price", d -> d.price);
private String name;
private Function<Detail, Object> valueExtractor;
private DetailQueryParams(String name,
Function<Detail, Object> valueExtractor) {
this.name = name;
this.valueExtractor = valueExtractor;
}
public static Predicate<Detail> mustMatchDetailValues(
Function<String, Optional<String>> queryParamGetter) {
return Arrays.asList(values()).stream()
.map(p -> queryParamGetter.apply(p.name)
.map(q -> (Predicate<Detail>)
d -> String.valueOf(p.valueExtractor.apply(d)).equals(q))
.orElse(d -> true))
.reduce(Predicate::and)
.orElse(d -> true);
}
}
And then, assuming that you can access query params by e.g. request.getQueryParam(String name) which returns a String value or null, use the code by calling the following:
details.stream()
.filter(DetailQueryParams.mustMatchDetailValues(
name -> Optional.ofNullable(request.getQueryParam(name))))
.collect(Collectors.toList());
What the method basically does is:
- for each possible query param
- get its value from the request
- if value is present build predicate which
- gets field value from detail object and convert to string
- check that both strings (queried and extracted) matches
- if value is not present return predicate that always returns true
- combine resulting predicates using and
- use always true as fallback (which here never actually happens)
Of course this could also be extended to generate predicates depending on the actual value type instead of comparing strings so that e.g. ranges could be requested and handled via "priceMin" and/or "priceMax".
For Lambda details -> condition. So we can specify how many required.
details.stream().filter(detail-> detail.getItem()==item && detail.getName()==name && ...)