1
2
3
4
5 package com.qulice.pmd.rules;
6
7 import java.util.Map;
8 import java.util.Set;
9 import java.util.stream.IntStream;
10 import net.sourceforge.pmd.lang.ast.Node;
11 import net.sourceforge.pmd.lang.java.ast.ASTAmbiguousName;
12 import net.sourceforge.pmd.lang.java.ast.ASTBlock;
13 import net.sourceforge.pmd.lang.java.ast.ASTClassType;
14 import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
15 import net.sourceforge.pmd.lang.java.ast.ASTExpression;
16 import net.sourceforge.pmd.lang.java.ast.ASTFieldAccess;
17 import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
18 import net.sourceforge.pmd.lang.java.ast.ASTTypeExpression;
19 import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
20 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
21
22
23
24
25
26
27
28
29 final class UnnecessaryLocalSkips {
30
31
32
33
34
35 private static final Set<String> TIME_TYPES = Set.of(
36 "Instant", "LocalDate", "LocalDateTime", "LocalTime",
37 "ZonedDateTime", "OffsetDateTime", "OffsetTime",
38 "Year", "YearMonth", "MonthDay", "Clock"
39 );
40
41
42
43
44
45 private static final Map<String, Set<String>> FRESH_STATE_CALLS = Map.of(
46 "System", Set.of("currentTimeMillis", "nanoTime"),
47 "UUID", Set.of("randomUUID"),
48 "Math", Set.of("random")
49 );
50
51 private UnnecessaryLocalSkips() {
52 }
53
54
55
56
57
58
59
60
61
62
63
64 static boolean acrossBoundary(
65 final ASTBlock block,
66 final String name,
67 final int crossings
68 ) {
69 return crossings != block
70 .descendants(ASTVariableAccess.class)
71 .filter(ref -> name.equals(ref.getName()))
72 .count();
73 }
74
75
76
77
78
79
80
81
82
83 static boolean freshState(final ASTExpression init) {
84 final boolean result;
85 if (init instanceof ASTMethodCall call) {
86 result = UnnecessaryLocalSkips.freshStateCall(call);
87 } else if (init instanceof ASTConstructorCall call) {
88 final ASTClassType type = call.getTypeNode();
89 result = type != null && "Date".equals(type.getSimpleName());
90 } else {
91 result = false;
92 }
93 return result;
94 }
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 static boolean interveningCall(
112 final ASTVariableDeclarator variable,
113 final ASTVariableAccess use
114 ) {
115 final ASTExpression init = variable.getInitializer();
116 final boolean found;
117 if (init instanceof ASTMethodCall
118 || init instanceof ASTConstructorCall) {
119 found = UnnecessaryLocalSkips.intervenes(variable, use);
120 } else if (init instanceof ASTFieldAccess access) {
121 found = UnnecessaryLocalSkips.callsQualifier(variable, use, access);
122 } else {
123 found = false;
124 }
125 return found;
126 }
127
128 private static boolean intervenes(
129 final ASTVariableDeclarator variable,
130 final ASTVariableAccess use
131 ) {
132 boolean found = false;
133 final ASTBlock block = variable.ancestors(ASTBlock.class).first();
134 if (block != null) {
135 final Node decl = UnnecessaryLocalSkips.childOf(block, variable);
136 final Node consumer = UnnecessaryLocalSkips.childOf(block, use);
137 found = decl != null && consumer != null
138 && consumer.getIndexInParent() > decl.getIndexInParent() + 1;
139 }
140 return found;
141 }
142
143 private static boolean callsQualifier(
144 final ASTVariableDeclarator variable,
145 final ASTVariableAccess use,
146 final ASTFieldAccess access
147 ) {
148 boolean found = false;
149 final ASTBlock block = variable.ancestors(ASTBlock.class).first();
150 final String qualifier = UnnecessaryLocalSkips.qualifierImage(
151 access.getQualifier()
152 );
153 if (block != null && qualifier != null) {
154 final Node decl = UnnecessaryLocalSkips.childOf(block, variable);
155 final Node consumer = UnnecessaryLocalSkips.childOf(block, use);
156 if (decl != null && consumer != null) {
157 found = IntStream.range(
158 decl.getIndexInParent() + 1, consumer.getIndexInParent()
159 ).anyMatch(
160 idx -> UnnecessaryLocalSkips.calls(
161 block.getChild(idx), qualifier
162 )
163 );
164 }
165 }
166 return found;
167 }
168
169 private static boolean calls(final Node stmt, final String qualifier) {
170 return stmt.descendants(ASTMethodCall.class).toStream().anyMatch(
171 call -> qualifier.equals(
172 UnnecessaryLocalSkips.qualifierImage(call.getQualifier())
173 )
174 );
175 }
176
177 private static Node childOf(final ASTBlock block, final Node inner) {
178 Node current = inner;
179 while (current != null && !block.equals(current.getParent())) {
180 current = current.getParent();
181 }
182 return current;
183 }
184
185 private static boolean freshStateCall(final ASTMethodCall call) {
186 final String name = call.getMethodName();
187 final String qualifier = UnnecessaryLocalSkips.qualifierImage(
188 call.getQualifier()
189 );
190 boolean fresh = false;
191 if (qualifier != null) {
192 final boolean known = UnnecessaryLocalSkips.FRESH_STATE_CALLS
193 .getOrDefault(qualifier, Set.of()).contains(name);
194 final boolean clock = "now".equals(name)
195 && UnnecessaryLocalSkips.TIME_TYPES.contains(qualifier);
196 fresh = known || clock;
197 }
198 return fresh;
199 }
200
201 private static String qualifierImage(final ASTExpression expr) {
202 final String result;
203 if (expr instanceof ASTAmbiguousName name) {
204 result = name.getName();
205 } else if (expr instanceof ASTVariableAccess access) {
206 result = access.getName();
207 } else if (expr instanceof ASTTypeExpression type
208 && type.getTypeNode() instanceof ASTClassType klass) {
209 result = klass.getSimpleName();
210 } else {
211 result = null;
212 }
213 return result;
214 }
215 }