Check all objects inside collection should have length greater than 0 - java

I have a code snippet like this
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
for (final SpecificationData data : target.getSpecifications()) {
if (StringUtils.isNotEmpty(data.getModelName())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getModelNumber())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getMaterial())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getColour())) {
productLinks.add(DETAILS);
break;
}
}
}
As you can see, I am iterating a collection and doing a check in order to populate the link "details" in front end. The idea is that I need to populate this link at least one of the attribute length inside current object should be > 0. Because of the fact, I have used so many break statement, this snippet is failing in sonar build process
What do I need? I request you guys to share me the simplest version of the above code or refactored code using latest JDK and yes we are using JDK 11 and I am not pretty sure about the methods that I need to use for this kind of check.
If there is no other alternatives how to overcome this "Loops should not contain more than a single "break" or "continue" statement" sonar issue.
Appreciate your time and effort on this.

The easiest solution could be just to join multiple if statements into one though it may not be helpful against Sonar rules :)
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
for (final SpecificationData data : target.getSpecifications()) {
if (StringUtils.isNotEmpty(data.getModelName())
|| StringUtils.isNotEmpty(data.getModelNumber())
|| StringUtils.isNotEmpty(data.getMaterial())
|| StringUtils.isNotEmpty(data.getColour())
) {
productLinks.add(DETAILS);
break;
}
}
}
However, you may use stream operations such as filter and findFirst like this without any for loop and break statements:
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
target.getSpecifications().stream()
.filter(x ->
Stream.of(x.getModelName(), x.getModelNumber(), x.getMaterial(), x.getColour())
.filter(StringUtils::isNotEmpty)
.findFirst()
.isPresent()
)
.findFirst()
.ifPresent(x -> productLinks.add(DETAILS));
}
UPDATE
For this specific case it is also possible to use flatMap to detect any first non-empty property and perform an action:
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
target.getSpecifications().stream()
.flatMap(x -> Stream.of(x.getModelName(), x.getModelNumber(), x.getMaterial(), x.getColour()))
.filter(StringUtils::isNotEmpty)
.findFirst()
.ifPresent(x -> productLinks.add(DETAILS));
}

Related

Using Streams to replace loops

I have the following code:
boolean found = false;
for (Commit commit : commits.values()) {
if (commit.getLogMessage().compareTo(commitMessageToSearch) == 0) {
System.out.println(commit.getSha());
found = true;
}
}
if (!found) {
System.out.println("aksdhlkasj");
}
Is there some way to write this succinctly using streams or anything else in Java
You can use Stream#filter along with Stream#findFirst.
System.out.println(commits.values().stream()
.filter(commit -> commit.getLogMessage().compareTo(commitMessageToSearch) == 0)
.findFirst().map(Commit::getSha).orElse("aksdhlkasj"));
In case you want to print out all the occurrences and print some String only in case, there was no item found, I am afraid there is no way other than collecting all the relevant sha values into a list and checking for its emptiness using Optional:
commits.values()
.stream()
.filter(commit -> commit.getLogMessage().compareTo(commitMessageToSearch) == 0)
.map(Commit::getSha)
.peek(System.out::println)
.collect(Collectors.collectingAndThen(Collectors.toList(), Optional::of))
.filter(List::isEmpty)
.ifPresent(emptyList -> System.out.println("aksdhlkasj"));
Although the intermediate output Optional<List<?>> is against common sense, it helps the further processing using Optinal and comfortably handling the case the list is empty.
However, this form is in my opinion more readable:
List<String> shaList = commits.values()
.stream()
.filter(commit -> commit.getLogMessage().compareTo(commitMessageToSearch) == 0)
.map(Commit::getSha)
.peek(System.out::println)
.collect(Collectors.toList());
if (shaList.isEmpty()) {
System.out.println("aksdhlkasj");
}
The following options are available:
Use StreamEx library (maven repo) and its quasi-intermediate operation StreamEx::ifEmpty:
import one.util.streamex.StreamEx;
// ...
String msgSearch = "commit message";
StreamEx.of(commits.values())
.filter(c -> c.getLogMessage().compareTo(msgSearch) == 0)
.ifEmpty("no commit message found: " + msgSearch)
.map(Commit::getSha)
.forEach(System.out::println);
Plus: very concise and clean, single run
Minus: use of a 3rd-party lib
Check the contents of the stream using short-circuiting match without redundant collecting into a list just to check if it's empty
if (commits.values().stream()
.map(Commit::getLogMessage) // equals should be equivalent to compareTo == 0
.anyMatch(msgSearch::equals)
) {
commits.values().stream()
.filter(c -> c.getLogMessage().compareTo(msgSearch) == 0)
.map(Commit::getSha)
.forEach(System.out::println);
} else {
System.out.println("no commit message found: " + msgSearch);
}
Plus: using standard API (no 3-rd party) without side-effect
Minuses: too verbose, in the worst case (long list with the matching element in the end) double iteration of the stream
Use effectively final variable and its setting from the stream as a side effect.
Disclaimer: usage of stateful streams and side effects are not recommended in general:
The best approach is to avoid stateful behavioral parameters to stream operations entirely...
Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards.
A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care
Thus, if AtomicBoolean is carefully selected as a thread-safe and fast container of a boolean value for found flag, which is only set to true from inside the stream (never reset), the following solution may be offered and the risks of safety and performance are mitigated:
// effectively final and thread-safe container boolean
AtomicBoolean found = new AtomicBoolean();
commits.values().stream()
.filter(c -> c.getLogMessage().compareTo(commitMessageToSearch) == 0)
.map(Commit::getSha)
.forEach(sha -> {
found.set(true);
System.out.println(sha);
});
if (!found.get()) {
System.out.println("none found");
}
Plus: using standard API (no 3rd-party library), single run, no redundant collection
Minus: using side-effect, discouraged by purists, just mimics of for-each loop

How could I simplify this with Java stream library?

for (Issue issue : issues) {
if (issue.getSubtasks().spliterator().estimateSize() > 0) {
alreadyCreated = false;
for (Subtask subtask : issue.getSubtasks()) {
if (subtask.getSummary().contains("sometext")) {
alreadyCreated = true;
break;
}
}
if (!alreadyCreated) {
ids.add(issue.getKey());
}
} else {
ids.add(issue.getKey());
}
}
I'm not so expert with Java stream API, but I'm pretty sure there's some way to simplify the code above with using lambda expressions. Would be a great help to understand it even better!
Some notes:
Issues and getSubtasks() are returning back Iterable<> types.
You can use filter to remove unwanted elements and map to get the key, then collect to a List.
StreamSupport.stream(issues.spliterator(), false).filter(x ->
StreamSupport.stream(x.getSubtasks().spliterator(), false)
.noneMatch(y -> y.getSummary().contains("sometext"))
).map(Issue::getKey).collect(Collectors.toList());
I can't be certain what is streamable in your example so I'm going to provide an alternate solution that doesn't require streams but is at least, if not more, efficient. It just uses a different technique but essentially the same logic.
if size <= 0, then the for loop is skipped and the key is added.
if size > 0 then then for loop is excecuted. Then if any of the summaries contains the text, the outer loop proceeds normally, otherwise, the loop falls thru ant the key is added.
outer:
for (Issue issue : issues) {
if (issue.getSubtasks().spliterator().estimateSize() > 0) {
for (Subtask subtask : issue.getSubtasks()) {
if (subtask.getSummary().contains("sometext")) {
continue outer;
}
}
}
ids.add(issue.getKey());
}
}
You're adding the issue key to ids if there exists no subtask with a summary containing "sometext."
issues.stream()
.filter(i -> i.getSubtasks().stream()
.noneMatch(s -> s.getSummary().contains("sometext")))
.map(Issue::getKey)
.forEach(ids::add);
I think the subtasks size check is at least superfluous and possibly dangerous, if it's possible for it to estimate the size as zero when subtasks exist. But if getSubtasks() returns some non-collection that can't easily be streamed and this estimateSize() call is necessary then that just changes things a little bit:
issues.stream()
.filter(i -> {
Spliterator<Subtask> split = i.getSubtasks().spliterator();
return split.estimateSize() == 0 ||
StreamSupport.stream(split, false)
.noneMatch(s -> s.getSummary().contains("sometext"));
})
.map(Issue::getKey)
.forEach(ids::add);

Fix for Cognitive Complexity for multiple if else conditions in Java

This is my code which is working fine. But when I push the code, the sonarqube quality gate fails due to Cognitive Complexity. Any ideas on fixing this sonar issue
if (bbResponse.getEmails() != null && !bbResponse.getEmails().isEmpty()) {
bbResponse.getEmails().stream().forEach((BBEmail bbEmail) -> {
if ("CHK".equals(bbEmail.getSEQ())) {
//CODE
} else if ("CHT".equals(bbEmail.getSEQ())) {
//CODE
} else if ("MYT".equals(bbEmail.getSEQ())) {
//CODE
} else {
throw new IllegalStateException();
}
});
}
Some ideas (don't know whether they satisfy Sonarqube):
The test bbResponse.getEmails().isEmpty() isn't necessary. forEach() on an empty list is perfectly valid, will execute its body zero times.
As written in comments already, you can replace the string comparison conditionals by a switch statement.
You can refactor the lambda expression into a method of its own and use a method reference in the forEach() call.
You can refactor the blocks given as //CODE in your post, into methods of their own, if they are longer than a handful of lines.
By the way:
While Sonarqube surely gives valuable advice, I'd never make it a hard quality gate.
Having an automat with somewhat obscure rules decide on acceptable code style doesn't seem like a good idea to me. We all want clean, human-readable code, and that's not the same as Sonarqube-compatible code.
E.g. Sonarqube isn't able to judge the most important aspect of readability: the naming of classes, fields, variables and so on. And your issue shows that e.g. the complexity rule rejects code that no developer would ever judge as "difficult to read" (unless the "CODE" blocks that you omitted are overly long).
A check for non empty list may be redundant if you use stream(), _as well as forEach may be used without calling .stream()
It is possible to prepare a map of consumer methods for specific codes
Use Optional::ifPresentOrElse when getting a nullable consumer from the map or throw an exception.
static void handleEmails(List<BBEmail> emails) {
if (null != emails) {
Map<String, Consumer<BBEmail>> codes = Map.of(
"CHK", MyClass::processChkEmail,
"CHT", MyClass::processChtEmail,
"MYT", MyClass::processMytEmail
);
emails.orEach(bbEmail ->
Optional.ofNullable(codes.get(bbEmail.getSEQ()))
.ifPresentOrElse(consumer -> consumer.accept(bbEmail),
() -> {throw new IllegalStateException();}
)
);
}
}
static void processChkEmail(BBEmail email) {
// TODO CHK
}
static void processChtEmail(BBEmail email) {
// TODO CHT
}
static void processMytEmail(BBEmail email) {
// TODO MYT
}
This should resolve Sonar issue but I am not quite sure it increases readability for humans :)
Another previously mentioned option is switch statement -- which can be even more concise for Java 12+ syntax without break statements:
static void handleEmailSwitchJava12(List<BBEmail> emails) {
if (null != emails) {
emails.forEach(bbEmail -> {
switch (bbEmail.getSEQ()) {
case "CHK" -> processChkEmail(bbEmail);
case "CHT" -> processChtEmail(bbEmail);
case "MYT" -> processMytEmail(bbEmail);
default -> throw new IllegalStateException("Invalid SEQ code: " + bbEmail.getSEQ());
}
});
}
}

Use of lambda expression in java 8

I want to refactor this code to use lambda expression java 8
for(int i = 0; i < customers.getCUSTOMER().size(); i++){
if (customers.getCUSTOMER().get(i).getINCOME().getGROSSMONTH1().toBigInteger()
< customers.getCUSTOMER().get(i).getINCOME().getNETTSALMONTH1().toBigInteger()){
log.error("")
throw new RuntimeException();
}
}
You are iterating over the elements of customers.getCUSTOMER(), which appears to be a List (and I assume for the purposes of this answer that it is one). Presumably, then, you're going to process that list's stream:
customers.getCUSTOMER().stream()
you're using the getINCOME() of each element twice, and not any other aspect of it at all, so perhaps you want to map elements via that method. Supposing that the elements of the customer list are of type Customer, that might be
.map(Customer::getINCOME)
There are various ways to go from there, but since you're throwing an exception when a condition is satisfied, I'd personally go with Stream.anyMatch():
.anyMatch(x -> x.getGROSSMONTH1().toBigInteger().compareTo(
x.getNETTSALMONTH1().toBigInteger()) < 0)
That produces a boolean result that tells you whether any element satisfies the condition, and it stops looking as soon as it finds one that does (and the predicate for the anyMatch() is a lambda). You would use it in an if expression similar to the one you have now:
if (customers.getCUSTOMER().stream()
.map(Customer::getINCOME)
.anyMatch(x -> x.getGROSSMONTH1().toBigInteger().compareTo(
x.getNETTSALMONTH1().toBigInteger()) < 0) {
log.error("")
throw new RuntimeException();
}
Since you're throwing a RuntimeException, it would also be possible to do that with a lambda, inside the stream, but that's not generally possible if you're throwing a checked exception. In the checked-exception case, something along the lines presented here is usually needful.
On the other hand, if you want to log information about the element that failed then you would need to do that inside the stream. Additionally, in that case you might want to skip the map(). You could instead filter() based on the predicate, and findFirst() among the elements, if any, remaining (or findAny() if you don't care whether it's the first that you report on). The result is an Optional, which you can process, if present, with another lambda:
customers.getCUSTOMER().stream()
.filter(x -> x.getINCOME().getGROSSMONTH1().toBigInteger().compareTo(
x.getINCOME().getNETTSALMONTH1().toBigInteger()) < 0)
.findFirst()
.ifPresent(x -> {
log.error(x.getName() + " invalid: net salary is larger than gross")
throw new RuntimeException();
});
Note that this does not solve the checked-exception problem. You cannot throw a checked exception from inside the ifPresent lambda.
customers.getCUSTOMER().stream().forEach(customer -> {
if(customer.getINCOME().getGROSSMONTH1().toBigInteger() < customer.getINCOME().getNETTSALMONTH1().toBigInteger()){
log.error("");
throw new RuntimeException();
}
});
You should also try renaming your methods using camelcase, e.g getIncome() to make it easier to read and conform to normal Java writing standards.
customers.getCUSTOMER().forEach(customer -> {
if (customer.getINCOME().getGROSSMONTH1().toBigInteger() <
customer.getINCOME().getNETTSALMONTH1().toBigInteger()) {
log.error("")
throw new RuntimeException();
}
});
A simple way
Another way using stream and Optional:
customers.stream().filter(customer ->
customer.getINCOME().getGROSSMONTH1().toBigInteger() <
customer.getINCOME().getNETTSALMONTH1().toBigInteger())
.findAny().ifPresent(c -> {
log.error("")
throw new RuntimeException();
});;
By using java-8 streams, use filter for condition and findFirst to terminate after one match
customers.getCUSTOMER().stream()
.filter(c->c.getINCOME().getGROSSMONTH1().toBigInteger() < c.getINCOME().getNETTSALMONTH1().toBigInteger())
.findFirst()
.ifPresent(cu->{
log.error("")
throw new RuntimeException();
});
What you need in this case is a BiPredicate<TypeOfMonths> which will look like this
BiPredicate<TypeOfMonths> biPredicate = (tom1, tom2) -> tom1.toBigInteger() < tom2.toBigInteger();
Then use it like this
boolean shouldThrowException = customers.getCUSTOMER()
.stream()
.anyMatch(cust -> {
TypeOfMonth tom1 = getINCOME().getGROSSMONTH1();
TypeOfMonth tom2 = getINCOME()..getNETTSALMONTH1();
return biPredicate.test(tom1, tom2);
});
if (shouldThrowException) {
log.error("");
throw new RuntimeException();
}

Multiple if conditions ,how to optimize it

i am working on a large application which has differnt conditions(ifs) and different methods associated with it.Please suggest a way to optimize below mentioned code(reduce as much of nested ifs as possible).I would like the code to able incorporate any other specific condition with ease.(I use property files to fetch conditions)
.
public getParameter(String parameter)
{
if(parameter=specific condition1
||parameter=specific condition2)
{
do this
}
if(parameter=specific condition3)
{
do something else
}
if(parameter=specific condition4)
{
do something else
}
if(parameter=general condition)
{
do something else
}
else {
do something else
}
Say you have a property file with
do1=val1,val2,val3
do2=val5,val6,val7
(it seems you have a fixed set of actions)
You may load it with
HashMap<String, HashSet<String>> rules = new HashMap<String, HashSet<String>>();
for(String key : properties.stringPropertyNames()) {
HashSet<String> set = new HashSet<String>();
set.addAll(Arrays.asList(properties.getProperty(key).split(",")));
rules.put(key, set);
}
Now you have a map linking action names ("do1", etc.) to sets of possible values ("val1", etc.).
You may execute the rules with
if (rules.get("do1").contains(parameter)) do1();
if (rules.get("do2").contains(parameter)) do2();
(I let you add the necessary checks to avoid null pointer exceptions for example)
you could use a switch case.
http://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html

Categories