Java Stream processing with if / checking stream state - java

Given: list of Customers (with Supplier and Agency fields), String agency, String supplier.
Goal: check if any customer supports given agency AND given supplier.
I have a stream that needs to be filtered twice (by two values).
If stream is empty after first filtering, I need to check it and throw exception. If it's not empty, I need to process it through second filter (and then check again if it's not empty).
I want to avoid collecting stream to lists if it's possible (and I can't use anyMatch or count methods because they are terminal)
Currently my code look's like:
void checkAgencySupplierMapping(String agency, String supplier) {
List<Customers> customersFilteredByAgency = allCustomers.stream()
.filter(customer -> customer.getAgency().equals(agency))
.collect(toList());
if (customersFilteredByAgency.isEmpty()) throw new AgencyNotSupportedException(agency);
customersFilteredByAgency.stream()
.filter(customer -> customer.getSupplier().equals(supplier))
.findFirst().orElseThrow(() -> throw new SupplierNotSupportedException(supplier);
}
In this example I skipped some technical details about filtering (eg. parsing Supplier to String).
And I want to achieve something like this:
void checkAgencySupplierMapping(String agency, String supplier) {
allCustomers.stream()
.filter(customer -> customer.getAgency().equals(agency))
.ifEmpty( () -> throw new AgencyNotSupportedException(agency) )
.filter( customer -> customer.getSupplier().equals(supplier)
.ifEmpty( () -> throw new SupplierNotSupportedException(supplier); // or findFirst().orElseThrow...
}
Is there any Java 8 feature that will let me checking my Stream status without terminating it?

The code below is a bit ugly but work like you wish.
First we need to count how many agency of customers match with and then try found the first one supplier match. If there are no matches throw an exception, but here you will check if the cause is that no agency clients were found in order to throw the correct excaption.
AtomicInteger countAgencyMatches = new AtomicInteger(0);
allCustomers.stream()
.filter(customer -> {
if (customer.getAgency().equals(agency)) {
countAgencyMatches.incrementAndGet();
return true;
}
return false;
})
.filter(customer -> customer.getSupplier().equals(supplier))
.findFirst()
.orElseThrow(() -> {
if (countAgencyMatches.get() == 0) {
return new AgencyNotSupportedException(agency);
}
return new SupplierNotSupportedException(supplier);
});

Related

Java List of CompletableFutures to CompletableFuture List with allOf()

I have a piece of asynchronous code which contains more methods and I need to make it return CompletableFuture<List> in the end.
I need to use 2 methods:
the first method getConfigsByType() returns a Flux of type Config
the second one, which needs to be applied to every individual Config object, returns CompletableFuture of type Config.
I want to use allOf() in order to get the expected result, but I have an error and I do not know why: "no instance(s) of type variable(s) U exist so that Boolean conforms to CompletionStage". The error is at this line: .thenCompose(segmentedConfig -> finalEvents.add(segmentedConfig));
private CompletableFuture<List<Config>> getConfigs(User user) {
Queue<Config> finalEvents = new ConcurrentLinkedQueue<>();
List<CompletableFuture<Config>> completableFutureList = admin.getConfigsByType(configurationProperties.getEvents()) // returns Flux<Config>
.map(config -> {
return segmentConfig(config, user) // returns CompletableFuture<Config>
.thenCompose(segmentedConfig -> finalEvents.add(segmentedConfig));
})
.collect(Collectors.toList());
return allOf(completableFutureList)
.thenApply(list -> finalEvents);
private CompletableFuture<Void> allOf(List<CompletableFuture<Config>> futuresList) {
return CompletableFuture.allOf(futuresList.toArray(new CompletableFuture[0]));
}
private CompletableFuture<Config> segmentConfig(Config config, User user) {
return configurationApi.getSegmentedConfig(new DefaultCombinedConfigProvider<>(config), user);
}
What am I doing wrong?
You can not produce the list of results before the future created by allOf(completableFutureList) has been completed. Further, a Queue<Config> won’t become a List<Config>.
So, remove your attempt to produce the result list from the stream operation that produces the List<CompletableFuture<Config>>. Then, add an actual operation producing the result list to allOf(completableFutureList).
private CompletableFuture<List<Config>> getConfigs(User user) {
List<CompletableFuture<Config>> completableFutureList
= admin.getConfigsByType(configurationProperties.getEvents())
.map(config -> segmentConfig(config, user))
.collect(Collectors.toList());
return CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[0]))
.thenApply(voidArg -> completableFutureList.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}

Using Java Optional within stream mapping

I have code like this:
public void processList(List<String> list) {
for (String item : list) {
Object obj = getObjectForString(item);
if (obj != null) {
doSomethingWithObject(obj);
} else {
System.err.println("Object was null for " + item);
}
}
}
Ideally I would like to streamline this and avoid the null check using list.stream().map( *blah, blah, blah* ), and doSomethingWithObject if the object is not null, but log the error otherwise (by using the orElse method on an optional). I'm not super savvy with this Java 8 functionality and not sure if there is a nice, slick way to do what I want here or not. Suggestions?
Edit to add a failed attempt at this:
list.stream()
.map(p -> getObjectForString(p))
.map(Optional::ofNullable)
.forEach(
p -> p.ifPresentOrElse(
r -> doSomethingWithObject(r),
() -> System.err.println("Object was null")
));
Even if that code behaved the way I want, it still doesn't append the String from the original list to the error message as I would like it to. But maybe that's too much complexity to try to accomplish with streams like this.
we should propagate the item even after conversion. The slick way is using tuple or pair.
I used Tuple from vavr functional library to do the same. And below is the code for your reference
list.stream()
.map(p -> Tuple.of(p, getObjectForString(p)).map2(Optional::ofNullable))
.forEach(p -> p._2.ifPresentOrElse(
r -> doSomethingWithObject(r),
() -> System.err.println("Object was null" + p._1))
);
Another approach would be to collect the items in to separate 2 buckets/partitions based on if the item had an associated object or not. After that, process the 2 buckets as required:
final Boolean HAS_OBJECT = Boolean.FALSE;
Map<Boolean, List<String>> partitionedMap = list.stream()
.collect(Collectors.partitioningBy(item -> !Objects.isNull(getObjectForString(item))));
partitionedMap.get(HAS_OBJECT).stream()
.map(item -> getObjectForString(item))
.forEach(obj -> doSomethingWithObject(obj));
partitionedMap.get(!HAS_OBJECT)
.forEach(item -> System.err.println("Object was null for " + item));
Even though the below method does not avoid a null check as you wanted in your question, this is just another way to achieve the same result. (Only benefit is that it saves 1-2 lines of code!).
The below code uses Runnable (takes no arguments and returns nothing as well) along with Java 8's Function.
NOTE : I would still recommend the normal for loop :-), as I believe that the below might look fancy, but the for loop is more easy to understand in this particular case.
Function<String, Runnable> func = item -> {
Object obj = getObjectForString(item);
return (obj != null) ? ( () -> doSomethingWithObject(obj))
: ( () -> System.err.println("Object was null for " + item));
};
list.stream().map(func).forEach(Runnable::run);

Logging size of filtered stream

I want this stream to throw an exception if there are more than 1 results that survive the filter. Is there any way to achieve this?
One idea is to use .count(), but I'll have to create the stream again.
Optional<Result> filteredresultSet = results.stream()
.filter(c -> c.equals("TOMATO))
.findAny();
You can replace findAny() by reduce() and throw an exception in the accumulator:
Optional<String> filteredresultSet = results.stream()
.filter("TOMATO"::equals)
.reduce((s1, s2) -> {
throw new MyException();
});
An alternative to find out exactly how many elements are left and still retrieve the first one is to use a small array or custom object to store the count and first element:
results.stream().filter("TOMATO"::equals).collect(Container::new, (c, e) -> {
c.count++;
if (c.value == null)
{
c.value = e;
}
}, (c1, c2) -> c1.count += c2.count);
This would return you an instance of Container, or null if the stream is empty. You can then check the count and return an Optional of the value.
A simple way is to use .limit(2) on the stream to short-circuit after two matches and then collect to a list or array.
List<Result> filtered = results.stream()
.filter(c -> c.equals("TOMATO"))
.limit(2) // we don't need to know if there are more than 2 matches
.collect(toList());
if (filtered.size() > 1) {
// throw or log
}
return filtered.isEmpty() ? Optional.empty() : Optional.of(filtered.get(0));

Java8 filter and return if only element

How can I filter a list using java8 streams and return the found element if it is the only element in the filtered list, otherwise(if there are more which meet the condition, or there is no result that meets the condition) return for example an Optional.empty()
I would need something like this:
Suppose I have a:
List<String> list = Arrays.asList("Apple","Banana","Peach");
then I want:
Optional<String> string = list.stream()
.filter(item -> item.startsWith("A"))
.findOne();
I know I can do it by:
boolean singleElement = list.stream()
.filter(item -> item.startsWith("A"))
.count() == 1;
String string = null;
if(singleElement){
string = list.stream().filter(item -> item.startsWith("A")).iterator().next();
}
but I was wondering if I can do it in a single stream?
Is there any single stream solution?
Not very pretty, but you could limit the stream to 2 elements, collect those in a List, and see if that list has exactly one element. This still has more than one line, and has some overhead for creating the list, but that overhead is limited (to a list of size 2) and it does not have to iterate the stream twice, either.
List<String> tmp = list.stream()
.filter(item -> item.startsWith("A"))
.limit(2)
.collect(Collectors.toList());
Optional<String> res = tmp.size() == 1 ? Optional.of(tmp.get(0)) : Optional.empty();
(My other idea was to use reduce((s1, s2) -> null) after limit(2) and reduce any two matches to null, but instead of returning an Optional.empty this will just raise an Exception, i.e. it does not work, but maybe this triggers some better (working) ideas.)
Update: It seems like while reduce raises an Exceptions, Collectors.reducing does not, and instead returns an Optional.empty as desired, so this also works, as shown in this answer to a very similar question. Still, I'd add limit(2) to make it stop early:
Optional<String> res = list.stream()
.filter(item -> item.startsWith("A"))
.limit(2)
.collect(Collectors.reducing((s1, s2) -> null));
(If you like this last part, please upvote the original answer.)
You could use google Guava library's MoreCollectors.onlyElement as below:
List<String> list = Arrays.asList("Apple", "Banana", "Peach");
String string = null;
try {
string = list.stream()
.filter(item -> item.startsWith("A"))
.collect(MoreCollectors.onlyElement());
} catch (NoSuchElementException | IllegalArgumentException iae) {
System.out.println("zero or more than one elements found.");
}
Optional<String> res = string == null ? Optional.empty() : Optional.of(string);
Notice it throws NoSuchElementException if there is no element and it throws IllegalArgumentException if there are more than one elements.
I don't know if this counts as a single operation to you, but you can do :
Arrays.asList("Apple", "Banana", "Peach")
.stream()
.collect(Collectors.collectingAndThen(
Collectors.partitioningBy(
x -> x.startsWith("A")),
map -> {
List<String> list = map.get(Boolean.TRUE);
return list.size() == 1 ? Optional.of(list.get(0)) : Optional.empty();
}));

Reusing part of Stream mapping and filtering to compose two different results

I'd like to know if there is a good way of reusing a common stream operation that varies in the end for different outputs.
The example bellow is exactly what I'm trying to compact into a one-step operation:
public static DepartmentInfo extractDepartmentInfo(BaselinePolicy resource) throws ResourceProcessorError {
Function<Exception, Exception> rpe = e -> new ResourceProcessorError(e.getMessage());
List<String> parents =
Objects.requireNonNull(
Exceptions.trying(
() -> Arrays.asList(Exceptions.dangerous(resource::getParentIds).expecting(CMException.class).throwing(rpe))
.stream()
.map(cId -> Exceptions.dangerous(cId, resource.getCMServer()::getPolicy).expecting(CMException.class).throwing(rpe))
.filter(policy -> PagePolicy.class.isAssignableFrom(policy.getClass()))
.map(PagePolicy.class::cast)
.filter(page -> Exceptions.dangerous(page,
p -> Boolean.valueOf(p.getComponentNotNull(ComponentConstants.POLOPOLY_CLIENT,
ComponentConstants.IS_HOME_DEPARTMENT,
Boolean.FALSE.toString())).booleanValue())
.expecting(CMException.class).throwing(rpe))
.map(page -> Exceptions.dangerous(page, p -> p.getExternalId().getExternalId()).expecting(CMException.class).throwing(rpe)), ResourceProcessorError.class)
.collect(Collectors.toList()));
String externalId = parents.get(parents.size()-1).toString();
List<String> list =
Objects.requireNonNull(
Exceptions.trying(
() -> Arrays.asList(Exceptions.dangerous(resource::getParentIds).expecting(CMException.class).throwing(rpe))
.stream()
.map(cId -> Exceptions.dangerous(cId, resource.getCMServer()::getPolicy).expecting(CMException.class).throwing(rpe))
.filter(policy -> PagePolicy.class.isAssignableFrom(policy.getClass()))
.map(PagePolicy.class::cast)
.map(page ->
Exceptions.dangerous(page,
p -> p.getChildPolicy(PATH_SEGMENT) != null &&
StringUtils.hasLength(SingleValued.class.cast(p.getChildPolicy(PATH_SEGMENT)).getValue())?
SingleValued.class.cast(p.getChildPolicy(PATH_SEGMENT)).getValue(): p.getName()).expecting(CMException.class).throwing(rpe))
.filter(val -> val != null && !val.isEmpty()), ResourceProcessorError.class)
.collect(Collectors.toList()));
if(list.size() > 3) {
list = list.subList(list.size() - 3, list.size()-1);
}
switch(list.size()) {
case 0: {
throw new ResourceProcessorError("br.com.oesp.XMLRender.error.noProduct");
}
case 1: {
return DepartmentInfo.withProduct(list.get(0), externalId);
}
case 2: {
return DepartmentInfo.withProduct(list.get(0), externalId).withDepartment(list.get(1));
}
default: {
return DepartmentInfo.withProduct(list.get(0), externalId).withDepartment(list.get(1)).withSubDepartment(list.get(2));
}
}
}
Notice that the first step is repeated for both:
List<String> parents =
Objects.requireNonNull(
Exceptions.trying(
() -> Arrays.asList(Exceptions.dangerous(resource::getParentIds).expecting(CMException.class).throwing(rpe))
.stream()
.map(cId -> Exceptions.dangerous(cId, resource.getCMServer()::getPolicy).expecting(CMException.class).throwing(rpe))
.filter(policy -> PagePolicy.class.isAssignableFrom(policy.getClass()))
.map(PagePolicy.class::cast)
It's not only a problem for reading but specially because I'm redoing a heavy operation twice, meanwhile in a more imperative way I'd do it once.
There are two things you're trying to do:
avoid the redundant work of creating the input array
avoid the redundant code of the map/filter/map
The first is easy:
List<Id> list = Arrays.asList(Exceptions.dangerous(resource::getParentIds)
.expecting(CMException.class)
.throwing(rpe));
Now you can pull streams from this source twice without rematerializing it.
The next bit is simply a Function from List to Stream:
Function<List<Id>, Stream<Something>> asStream =
list -> list.stream().map(...).filter(...).map(...);
Now, just start your stream with this:
asStream.apply(list).moreStuff().moreStuff()

Categories