99import static net .sourceforge .pmd .util .CollectionUtil .setOf ;
1010
1111import java .util .Collection ;
12- import java .util .HashMap ;
1312import java .util .Map ;
1413import java .util .Set ;
1514import java .util .stream .Collectors ;
4140public class UnusedPrivateMethodRule extends AbstractIgnoredAnnotationRule {
4241
4342 private static final Set <String > SERIALIZATION_METHODS =
44- setOf ("readObject" , "readObjectNoData" , "writeObject" , "readResolve" , "writeReplace" );
43+ setOf ("readObject" , "readObjectNoData" , "writeObject" , "readResolve" , "writeReplace" );
4544
4645 @ Override
4746 protected Collection <String > defaultSuppressionAnnotations () {
4847 return listOf (
49- "java.lang.Deprecated" ,
50- "jakarta.annotation.PostConstruct" ,
51- "jakarta.annotation.PreDestroy" ,
52- "lombok.EqualsAndHashCode.Include"
48+ "java.lang.Deprecated" ,
49+ "jakarta.annotation.PostConstruct" ,
50+ "jakarta.annotation.PreDestroy" ,
51+ "lombok.EqualsAndHashCode.Include"
5352 );
5453 }
5554
@@ -62,73 +61,40 @@ public Object visit(ASTCompilationUnit file, Object param) {
6261 Set <String > methodsUsedByAnnotations = methodsUsedByAnnotations (file );
6362 Map <JExecutableSymbol , ASTMethodDeclaration > candidates = findCandidates (file , methodsUsedByAnnotations );
6463
65- // First, remove methods that are called from within the same class
66- removeMethodsCalledFromSameClass (file , candidates );
67-
6864 file .descendants ()
69- .crossFindBoundaries ()
70- .<MethodUsage >map (asInstanceOf (ASTMethodCall .class , ASTMethodReference .class ))
71- .forEach (ref -> {
72- OverloadSelectionResult selectionInfo = ref .getOverloadSelectionInfo ();
73- JExecutableSymbol calledMethod = selectionInfo .getMethodType ().getSymbol ();
74- if (calledMethod .isUnresolved ()) {
75- handleUnresolvedCall (ref , selectionInfo , candidates );
76- return ;
65+ .crossFindBoundaries ()
66+ .<MethodUsage >map (asInstanceOf (ASTMethodCall .class , ASTMethodReference .class ))
67+ .forEach (ref -> {
68+ OverloadSelectionResult selectionInfo = ref .getOverloadSelectionInfo ();
69+ JExecutableSymbol calledMethod = selectionInfo .getMethodType ().getSymbol ();
70+ if (calledMethod .isUnresolved ()) {
71+ handleUnresolvedCall (ref , selectionInfo , candidates );
72+ return ;
73+ }
74+ candidates .compute (calledMethod , (sym2 , reffed ) -> {
75+ if (reffed != null && ref .ancestors (ASTMethodDeclaration .class ).first () != reffed ) {
76+ // remove mapping, but only if it is called from outside itself
77+ return null ;
7778 }
78- candidates .compute (calledMethod , (sym2 , reffed ) -> {
79- if (reffed != null && ref .ancestors (ASTMethodDeclaration .class ).first () != reffed ) {
80- // remove mapping, but only if it is called from outside itself
81- return null ;
82- }
83- return reffed ;
84- });
79+ return reffed ;
8580 });
81+ });
8682
8783 for (ASTMethodDeclaration unusedMethod : candidates .values ()) {
8884 asCtx (param ).addViolation (unusedMethod , PrettyPrintingUtil .displaySignature (unusedMethod ));
8985 }
9086 return null ;
9187 }
9288
93- /**
94- * Remove methods that are called from within the same class from the candidates map.
95- * This handles the case where private methods call other private methods in the same class.
96- */
97- private void removeMethodsCalledFromSameClass (ASTCompilationUnit file , Map <JExecutableSymbol , ASTMethodDeclaration > candidates ) {
98- if (candidates .isEmpty ()) {
99- return ;
100- }
101-
102- // Create a map of method names to their symbols for faster lookup
103- Map <String , JExecutableSymbol > methodNameToSymbol = new HashMap <>();
104- for (JExecutableSymbol symbol : candidates .keySet ()) {
105- methodNameToSymbol .put (symbol .getPackageName (), symbol );
106- }
107-
108- // Check each candidate method for calls to other private methods in the same class
109- for (ASTMethodDeclaration method : candidates .values ()) {
110- method .descendants (ASTMethodCall .class )
111- .crossFindBoundaries ()
112- .forEach (call -> {
113- String calledMethodName = call .getMethodName ();
114- JExecutableSymbol calledSymbol = methodNameToSymbol .get (calledMethodName );
115- if (calledSymbol != null && candidates .containsKey (calledSymbol )) {
116- // This method calls another private method in the same class, so remove the called method
117- candidates .remove (calledSymbol );
118- }
119- });
120- }
121- }
122-
12389 private static void handleUnresolvedCall (MethodUsage ref , OverloadSelectionResult selectionInfo , Map <JExecutableSymbol , ASTMethodDeclaration > candidates ) {
12490 // If the type is may be an instance of this class, then the method may be
12591 // a call to a private method here. In that case we whitelist all methods
12692 // with that name.
12793 JTypeMirror receive = selectionInfo .getTypeToSearch ();
12894 boolean receiverMayBeInstanceOfThisClass =
12995 receive == null
130- || TypeOps .isSpecialUnresolved (receive )
131- || receive .equals (ref .getEnclosingType ().getTypeMirror ());
96+ || TypeOps .isSpecialUnresolved (receive )
97+ || receive .equals (ref .getEnclosingType ().getTypeMirror ());
13298
13399 if (receiverMayBeInstanceOfThisClass ) {
134100 candidates .values ().removeIf (it -> it .getName ().equals (ref .getMethodName ()));
@@ -143,43 +109,43 @@ private static void handleUnresolvedCall(MethodUsage ref, OverloadSelectionResul
143109 */
144110 private Map <JExecutableSymbol , ASTMethodDeclaration > findCandidates (ASTCompilationUnit file , Set <String > methodsUsedByAnnotations ) {
145111 return file .descendants (ASTMethodDeclaration .class )
146- .crossFindBoundaries ()
147- .filter (
148- it -> it .getVisibility () == Visibility .V_PRIVATE
149- && !hasIgnoredAnnotation (it )
150- && !hasExcludedName (it )
151- && !(it .getArity () == 0 && methodsUsedByAnnotations .contains (it .getName ())))
152- .collect (Collectors .toMap (
153- ASTMethodDeclaration ::getSymbol ,
154- m -> m
155- ));
112+ .crossFindBoundaries ()
113+ .filter (
114+ it -> it .getVisibility () == Visibility .V_PRIVATE
115+ && !hasIgnoredAnnotation (it )
116+ && !hasExcludedName (it )
117+ && !(it .getArity () == 0 && methodsUsedByAnnotations .contains (it .getName ())))
118+ .collect (Collectors .toMap (
119+ ASTMethodDeclaration ::getSymbol ,
120+ m -> m
121+ ));
156122 }
157123
158124 private static Set <String > methodsUsedByAnnotations (ASTCompilationUnit file ) {
159125 return file .descendants (ASTAnnotation .class )
160- .crossFindBoundaries ()
161- .toStream ()
162- .flatMap (UnusedPrivateMethodRule ::extractMethodsFromAnnotation )
163- .collect (Collectors .toSet ());
126+ .crossFindBoundaries ()
127+ .toStream ()
128+ .flatMap (UnusedPrivateMethodRule ::extractMethodsFromAnnotation )
129+ .collect (Collectors .toSet ());
164130 }
165131
166132 private static Stream <String > extractMethodsFromAnnotation (ASTAnnotation a ) {
167133 return Stream .concat (
168- a .getFlatValues ().toStream ()
169- .map (ASTMemberValue ::getConstValue )
170- .map (asInstanceOf (String .class ))
171- .filter (StringUtils ::isNotEmpty ),
172- NodeStream .of (a )
173- .filter (it -> TypeTestUtil .isA ("org.junit.jupiter.params.provider.MethodSource" , it )
174- && it .getFlatValue ("value" ).isEmpty ())
175- .ancestors (ASTMethodDeclaration .class )
176- .take (1 )
177- .toStream ()
178- .map (ASTMethodDeclaration ::getName )
134+ a .getFlatValues ().toStream ()
135+ .map (ASTMemberValue ::getConstValue )
136+ .map (asInstanceOf (String .class ))
137+ .filter (StringUtils ::isNotEmpty ),
138+ NodeStream .of (a )
139+ .filter (it -> TypeTestUtil .isA ("org.junit.jupiter.params.provider.MethodSource" , it )
140+ && it .getFlatValue ("value" ).isEmpty ())
141+ .ancestors (ASTMethodDeclaration .class )
142+ .take (1 )
143+ .toStream ()
144+ .map (ASTMethodDeclaration ::getName )
179145 );
180146 }
181147
182148 private boolean hasExcludedName (ASTMethodDeclaration node ) {
183149 return SERIALIZATION_METHODS .contains (node .getName ());
184150 }
185- }
151+ }
0 commit comments