diff --git a/CHANGELOG.md b/CHANGELOG.md
index e657561c..cc47da74 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/pom.xml b/pom.xml
index ae43a190..014b03b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,7 +78,7 @@
1.8
- 2.2.2
+ 2.2.3
https://repo1.maven.org/maven2
@@ -231,6 +231,10 @@
org.apache.maven.plugins
maven-compiler-plugin
3.13.0
+
+ 16
+ 16
+
org.apache.maven.plugins
diff --git a/src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java b/src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java
index eff99826..064272ab 100644
--- a/src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java
+++ b/src/it/java/org/greencodeinitiative/creedengo/java/integration/tests/GCIRulesIT.java
@@ -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;
@@ -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);
+ }
+
}
diff --git a/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSort.java b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSort.java
new file mode 100644
index 00000000..18b9c321
--- /dev/null
+++ b/src/it/test-projects/creedengo-java-plugin-test-project/src/main/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSort.java
@@ -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 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());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java
index 791f0cef..1f084e39 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/JavaCheckRegistrar.java
@@ -50,7 +50,8 @@ public class JavaCheckRegistrar implements CheckRegistrar {
FreeResourcesOfAutoCloseableInterface.class,
AvoidMultipleIfElseStatement.class,
UseOptionalOrElseGetVsOrElse.class,
- MakeNonReassignedVariablesConstants.class
+ MakeNonReassignedVariablesConstants.class,
+ UseFilterBeforeSort.class
);
/**
diff --git a/src/main/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSort.java b/src/main/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSort.java
new file mode 100644
index 00000000..8b6b04fe
--- /dev/null
+++ b/src/main/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSort.java
@@ -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 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 methodChain = extractChainedMethodNames(mit);
+
+ int sortedIndex = methodChain.indexOf("sorted");
+ int filterIndex = methodChain.indexOf("filter");
+
+ if (sortedIndex != -1 && filterIndex != -1 && sortedIndex < filterIndex) {
+ reportIssue(tree, "Use 'filter' before 'sorted' for better efficiency.");
+ }
+ }
+ }
+
+ private List extractChainedMethodNames(MethodInvocationTree terminalInvocation) {
+ List 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
+ 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);
+ }
+
+}
diff --git a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
index 059bf0f5..33fb1a7e 100644
--- a/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
+++ b/src/main/resources/org/greencodeinitiative/creedengo/java/creedengo_way_profile.json
@@ -18,6 +18,7 @@
"GCI78",
"GCI79",
"GCI82",
+ "GCI91",
"GCI94"
]
}
diff --git a/src/test/files/UseFilterBeforeSort.java b/src/test/files/UseFilterBeforeSort.java
new file mode 100644
index 00000000..1b5712ce
--- /dev/null
+++ b/src/test/files/UseFilterBeforeSort.java
@@ -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 .
+ */
+package org.greencodeinitiative.creedengo.java.checks;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+class UseFilterBeforeSort {
+ UseFilterBeforeSort() {
+ }
+
+ public void manipulateStream(List 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());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSortTest.java b/src/test/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSortTest.java
new file mode 100644
index 00000000..4d9a53d5
--- /dev/null
+++ b/src/test/java/org/greencodeinitiative/creedengo/java/checks/UseFilterBeforeSortTest.java
@@ -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();
+ }
+}