Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- [GCI91](https://github.com/green-code-initiative/creedengo-java/pull/111) New rule to detect if a sort method is called before a filter in stream

### Changed

- [#103](https://github.com/green-code-initiative/creedengo-java/pull/103) GCI69 Java : calls to hasMoreElements() and nextElement() methods from java.util.Enumeration interface aren't flagged anymore when called in a for loop
Expand Down
6 changes: 5 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
<google.re2j>1.8</google.re2j>

<!-- Version of creedengo rules specifications implemented by this plugin -->
<creedengo-rules-specifications.version>2.2.2</creedengo-rules-specifications.version>
<creedengo-rules-specifications.version>2.2.3</creedengo-rules-specifications.version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use the last one : 2.5.0


<!-- URL of the Maven repository where sonarqube will be downloaded -->
<test-it.orchestrator.artifactory.url>https://repo1.maven.org/maven2</test-it.orchestrator.artifactory.url>
Expand Down Expand Up @@ -231,6 +231,10 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this overriding of jdk versions ? it is already at 17...
I've just checked your PR with the deletion of this two overinf lines and UT et IT are OK.

<source>16</source>
<target>16</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.greencodeinitiative.creedengo.java.integration.tests;

import org.junit.jupiter.api.Test;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Issues;
import org.sonarqube.ws.Measures;

Expand Down Expand Up @@ -548,4 +549,15 @@ void testGCI94() {
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_1MIN);
}

@Test
void testGCI91() {
String filePath = "src/main/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSort.java";
String ruleId = "creedengo-java:GCI91";
String ruleMsg = "Use 'filter' before 'sorted' for better efficiency.";
int[] startLines = new int[]{11,16};
int[] endLines = new int[]{14, 20};

checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, Common.Severity.MAJOR, TYPE, EFFORT_5MIN);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.greencodeinitiative.creedengo.java.checks;

import java.util.List;
import java.util.stream.Collectors;

class UseFilterBeforeSort {
UseFilterBeforeSort() {
}

public void manipulateStream(final List<String> list) {
list.stream() // Noncompliant {{Use 'filter' before 'sorted' for better efficiency.}}
.sorted()
.filter(s -> s.startsWith("A"))
.collect(Collectors.toList());

list.stream() // Noncompliant {{Use 'filter' before 'sorted' for better efficiency.}}
.sorted()
.map(element -> element.toString())
.filter(s -> s.startsWith("A"))
.collect(Collectors.toList());

list.stream() // Compliant {{Use 'filter' before 'sorted' for better efficiency.}}
.filter(s -> s.startsWith("A"))
.sorted()
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public class JavaCheckRegistrar implements CheckRegistrar {
FreeResourcesOfAutoCloseableInterface.class,
AvoidMultipleIfElseStatement.class,
UseOptionalOrElseGetVsOrElse.class,
MakeNonReassignedVariablesConstants.class
MakeNonReassignedVariablesConstants.class,
UseFilterBeforeSort.class
);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.greencodeinitiative.creedengo.java.checks;

import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Rule(key = "GCI91")
public class UseFilterBeforeSort extends IssuableSubscriptionVisitor {
@Override
public List<Tree.Kind> nodesToVisit() {
return Collections.singletonList(Tree.Kind.METHOD_INVOCATION);
}

@Override
public void visitNode(Tree tree) {
if (tree instanceof MethodInvocationTree) {
MethodInvocationTree mit = (MethodInvocationTree) tree;

if (!isTerminalOperation(mit)) {
return; // Ignoring intermediate steps (filter, sorted, etc.)
}

List<String> methodChain = extractChainedMethodNames(mit);

int sortedIndex = methodChain.indexOf("sorted");
int filterIndex = methodChain.indexOf("filter");

if (sortedIndex != -1 && filterIndex != -1 && sortedIndex < filterIndex) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure about your algorithms ... I think in streams we can have several select method ... thus you can control only the first one ? or more complex, control all the stream and we want only control th second part ... for example :
stream
. filtrer()
.sorted()
. findAny() /// first part
. sort()
. filter()
. collect() // second part

what is the behaviour for examples with the same complexity (several parts) ?
can you add some such cases in your test resources files to control the behaviour ?

reportIssue(tree, "Use 'filter' before 'sorted' for better efficiency.");
}
}
}

private List<String> extractChainedMethodNames(MethodInvocationTree terminalInvocation) {
List<String> methodNames = new ArrayList<>();
ExpressionTree current = terminalInvocation;

while (current instanceof MethodInvocationTree) {
MethodInvocationTree methodCall = (MethodInvocationTree) current;
ExpressionTree methodSelect = methodCall.methodSelect();

if (methodSelect instanceof MemberSelectExpressionTree) {
MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) methodSelect;
methodNames.add(memberSelect.identifier().name());
current = memberSelect.expression(); // Go to the previous method
} else {
break;
}
}

Collections.reverse(methodNames); // Reverse to get right order in collection like stream → ... → collect
return methodNames;
}

private boolean isTerminalOperation(MethodInvocationTree tree) {
if (!(tree.methodSelect() instanceof MemberSelectExpressionTree)) {
return false;
}
String methodName = ((MemberSelectExpressionTree) tree.methodSelect()).identifier().name();
// Ajouter ici tous les terminaux connus
Copy link

Copilot AI Aug 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is in French. For consistency with the codebase, it should be in English: '// Add all known terminal operations here'

Suggested change
// Ajouter ici tous les terminaux connus
// Add all known terminal operations here

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please correct this feedback

return "collect".equals(methodName)
|| "forEach".equals(methodName)
|| "reduce".equals(methodName)
|| "count".equals(methodName)
|| "toArray".equals(methodName)
|| "anyMatch".equals(methodName)
|| "allMatch".equals(methodName)
|| "noneMatch".equals(methodName)
|| "findFirst".equals(methodName)
|| "findAny".equals(methodName);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"GCI78",
"GCI79",
"GCI82",
"GCI91",
"GCI94"
]
}
44 changes: 44 additions & 0 deletions src/test/files/UseFilterBeforeSort.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* creedengo - Java language - Provides rules to reduce the environmental footprint of your Java programs
* Copyright © 2024 Green Code Initiative (https://green-code-initiative.org/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.greencodeinitiative.creedengo.java.checks;

import java.util.List;
import java.util.stream.Collectors;

class UseFilterBeforeSort {
UseFilterBeforeSort() {
}

public void manipulateStream(List<String> list) {
list.stream() // Noncompliant {{Use 'filter' before 'sorted' for better efficiency.}}
.sorted()
.filter(s -> s.startsWith("A"))
.collect(Collectors.toList());

list.stream() // Noncompliant {{Use 'filter' before 'sorted' for better efficiency.}}
.sorted()
.otherMethod()
.filter(s -> s.startsWith("A"))
.collect(Collectors.toList());

list.stream() // Compliant {{Use 'filter' before 'sorted' for better efficiency.}}
.filter(s -> s.startsWith("A"))
.sorted()
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.greencodeinitiative.creedengo.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

public class UseFilterBeforeSortTest {
@Test
void testHasIssues() {
CheckVerifier.newVerifier()
.onFile("src/test/files/UseFilterBeforeSort.java")
.withCheck(new UseFilterBeforeSort())
.verifyIssues();
}
}