1313 * See the License for the specific language governing permissions and
1414 * limitations under the License.
1515 */
16- package org .openrewrite .java .migrate .search ;
16+ package org .openrewrite .java .migrate .search . threadlocal ;
1717
18+ import lombok .Getter ;
1819import lombok .Value ;
1920import org .jspecify .annotations .Nullable ;
2021import org .openrewrite .ExecutionContext ;
2324import org .openrewrite .TreeVisitor ;
2425import org .openrewrite .java .JavaIsoVisitor ;
2526import org .openrewrite .java .MethodMatcher ;
27+ import org .openrewrite .java .migrate .search .ThreadLocalTable ;
2628import org .openrewrite .java .search .UsesType ;
2729import org .openrewrite .java .tree .Expression ;
2830import org .openrewrite .java .tree .J ;
3335import java .nio .file .Path ;
3436import java .util .*;
3537
36- import static org .openrewrite .Preconditions .*;
38+ import static org .openrewrite .Preconditions .check ;
39+ import static org .openrewrite .Preconditions .or ;
40+
3741
3842public abstract class AbstractFindThreadLocals extends ScanningRecipe <AbstractFindThreadLocals .ThreadLocalAccumulator > {
3943
@@ -45,8 +49,7 @@ public abstract class AbstractFindThreadLocals extends ScanningRecipe<AbstractFi
4549 private static final MethodMatcher INHERITABLE_THREAD_LOCAL_SET = new MethodMatcher (INHERITED_THREAD_LOCAL_FQN + " set(..)" );
4650 private static final MethodMatcher INHERITABLE_THREAD_LOCAL_REMOVE = new MethodMatcher (INHERITED_THREAD_LOCAL_FQN + " remove()" );
4751
48- @ Nullable
49- protected transient ThreadLocalTable dataTable ;
52+ transient ThreadLocalTable dataTable = new ThreadLocalTable (this );
5053
5154 @ Value
5255 public static class ThreadLocalAccumulator {
@@ -77,9 +80,13 @@ public boolean hasDeclarations() {
7780
7881 public static class ThreadLocalInfo {
7982 private @ Nullable Path declarationPath ;
83+ @ Getter
8084 private boolean isPrivate ;
85+ @ Getter
8186 private boolean isStatic ;
87+ @ Getter
8288 private boolean isFinal ;
89+ @ Getter
8390 private boolean declared ;
8491 private final Set <Path > initMutationPaths = new HashSet <>();
8592 private final Set <Path > regularMutationPaths = new HashSet <>();
@@ -92,38 +99,37 @@ void setDeclaration(Path path, boolean priv, boolean stat, boolean fin) {
9299 this .declared = true ;
93100 }
94101
102+ /**
103+ * Records a mutation from an initialization context (constructor/static initializer).
104+ */
95105 void addInitMutation (Path path ) {
96106 initMutationPaths .add (path );
97107 }
98108
109+ /**
110+ * Records a regular (non-initialization) mutation.
111+ */
99112 void addRegularMutation (Path path ) {
100113 regularMutationPaths .add (path );
101114 }
102115
103- public boolean isPrivate () {
104- return isPrivate ;
105- }
106-
107- public boolean isStatic () {
108- return isStatic ;
109- }
110-
111- public boolean isFinal () {
112- return isFinal ;
113- }
114-
115- public boolean isDeclared () {
116- return declared ;
117- }
118-
119- public boolean hasAnyMutation () {
120- return !initMutationPaths .isEmpty () || !regularMutationPaths .isEmpty ();
116+ /**
117+ * Checks if there are no mutations (both init and regular).
118+ */
119+ public boolean hasNoMutation () {
120+ return initMutationPaths .isEmpty () && regularMutationPaths .isEmpty ();
121121 }
122122
123+ /**
124+ * Checks if there are only mutations from initialization contexts (constructors/static initializers).
125+ */
123126 public boolean hasOnlyInitMutations () {
124127 return !initMutationPaths .isEmpty () && regularMutationPaths .isEmpty ();
125128 }
126129
130+ /**
131+ * Checks if there are any mutations (both init and regular) from files other than the declaration file.
132+ */
127133 public boolean hasExternalMutations () {
128134 if (!declared || declarationPath == null ) {
129135 return true ; // Conservative
@@ -134,6 +140,9 @@ public boolean hasExternalMutations() {
134140 regularMutationPaths .stream ().anyMatch (p -> !p .equals (declarationPath ));
135141 }
136142
143+ /**
144+ * Checks if all mutations (both init and regular) are from the same file as the declaration.
145+ */
137146 public boolean isOnlyLocallyMutated () {
138147 if (!declared || declarationPath == null ) {
139148 return false ;
@@ -208,7 +217,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
208217 return method ;
209218 }
210219
211- @ Nullable String fqn = getFieldFullyQualifiedName (method .getSelect ());
220+ String fqn = getFieldFullyQualifiedName (method .getSelect ());
212221 if (fqn == null ) {
213222 return method ;
214223 }
@@ -228,7 +237,7 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ct
228237 return assignment ;
229238 }
230239
231- @ Nullable String fqn = getFieldFullyQualifiedName (assignment .getVariable ());
240+ String fqn = getFieldFullyQualifiedName (assignment .getVariable ());
232241 if (fqn == null ) {
233242 return assignment ;
234243 }
@@ -256,9 +265,10 @@ private boolean isInInitializationContext() {
256265
257266 private boolean isThreadLocalFieldAccess (Expression expression ) {
258267 if (expression instanceof J .Identifier ) {
259- return isThreadLocalType (((J .Identifier ) expression ).getType ());
260- } else if (expression instanceof J .FieldAccess ) {
261- return isThreadLocalType (((J .FieldAccess ) expression ).getType ());
268+ return isThreadLocalType (expression .getType ());
269+ }
270+ if (expression instanceof J .FieldAccess ) {
271+ return isThreadLocalType (expression .getType ());
262272 }
263273 return false ;
264274 }
@@ -279,7 +289,7 @@ private boolean isThreadLocalFieldAccess(Expression expression) {
279289 return null ;
280290 }
281291
282- @ Nullable JavaType owner = varType .getOwner ();
292+ JavaType owner = varType .getOwner ();
283293 if (!(owner instanceof JavaType .FullyQualified )) {
284294 return null ;
285295 }
@@ -294,22 +304,19 @@ private boolean isThreadLocalFieldAccess(Expression expression) {
294304 public TreeVisitor <?, ExecutionContext > getVisitor (ThreadLocalAccumulator acc ) {
295305 return check (acc .hasDeclarations (),
296306 new JavaIsoVisitor <ExecutionContext >() {
297- @ Override
298- public J .CompilationUnit visitCompilationUnit (J .CompilationUnit cu , ExecutionContext ctx ) {
299- if (dataTable == null ) {
300- dataTable = new ThreadLocalTable (AbstractFindThreadLocals .this );
301- }
302- return super .visitCompilationUnit (cu , ctx );
303- }
307+
304308
305309 @ Override
306310 public J .VariableDeclarations visitVariableDeclarations (J .VariableDeclarations multiVariable , ExecutionContext ctx ) {
307311 multiVariable = super .visitVariableDeclarations (multiVariable , ctx );
308312
309313 J .ClassDeclaration classDecl = getCursor ().firstEnclosing (J .ClassDeclaration .class );
314+ if (classDecl == null ) {
315+ return multiVariable ;
316+ }
310317
311318 for (J .VariableDeclarations .NamedVariable variable : multiVariable .getVariables ()) {
312- if (isThreadLocalType (variable .getType ()) && classDecl != null ) {
319+ if (isThreadLocalType (variable .getType ())) {
313320 String className = classDecl .getType () != null ?
314321 classDecl .getType ().getFullyQualifiedName () : "UnknownClass" ;
315322 String fieldName = variable .getName ().getSimpleName ();
@@ -320,18 +327,15 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m
320327 String message = getMessage (info );
321328 String mutationType = getMutationType (info );
322329
323- // Add to data table
324- if (dataTable != null ) {
325- dataTable .insertRow (ctx , new ThreadLocalTable .Row (
326- getCursor ().firstEnclosingOrThrow (SourceFile .class ).getSourcePath ().toString (),
327- className ,
328- fieldName ,
329- getAccessModifier (multiVariable ),
330- getModifiers (multiVariable ),
331- mutationType ,
332- message
333- ));
334- }
330+ dataTable .insertRow (ctx , new ThreadLocalTable .Row (
331+ getCursor ().firstEnclosingOrThrow (SourceFile .class ).getSourcePath ().toString (),
332+ className ,
333+ fieldName ,
334+ getAccessModifier (multiVariable ),
335+ getFieldModifiers (multiVariable ),
336+ mutationType ,
337+ message
338+ ));
335339
336340 return SearchResult .found (multiVariable , message );
337341 }
@@ -344,15 +348,17 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m
344348 private String getAccessModifier (J .VariableDeclarations variableDecls ) {
345349 if (variableDecls .hasModifier (J .Modifier .Type .Private )) {
346350 return "private" ;
347- } else if (variableDecls .hasModifier (J .Modifier .Type .Protected )) {
351+ }
352+ if (variableDecls .hasModifier (J .Modifier .Type .Protected )) {
348353 return "protected" ;
349- } else if (variableDecls .hasModifier (J .Modifier .Type .Public )) {
354+ }
355+ if (variableDecls .hasModifier (J .Modifier .Type .Public )) {
350356 return "public" ;
351357 }
352358 return "package-private" ;
353359 }
354360
355- private String getModifiers (J .VariableDeclarations variableDecls ) {
361+ private String getFieldModifiers (J .VariableDeclarations variableDecls ) {
356362 List <String > mods = new ArrayList <>();
357363 if (variableDecls .hasModifier (J .Modifier .Type .Static )) {
358364 mods .add ("static" );
@@ -366,8 +372,33 @@ private String getModifiers(J.VariableDeclarations variableDecls) {
366372 });
367373 }
368374
375+ /**
376+ * Determines whether a ThreadLocal should be marked based on its usage info.
377+ * Implementations should define the criteria for marking.
378+ * It is used to decide if a ThreadLocal variable should be highlighted in the results.
379+ * If an expected ThreadLocal instance is missing from the results, consider adjusting this method.
380+ *
381+ * @param info The ThreadLocalInfo containing usage details.
382+ * @return true if the ThreadLocal should be marked, false otherwise.
383+ */
369384 protected abstract boolean shouldMarkThreadLocal (ThreadLocalInfo info );
385+ /**
386+ * Generates a descriptive message about the ThreadLocal's usage pattern.
387+ * Implementations should provide context-specific messages.
388+ * It is used to receive the Markers message and the Data Tables detailed message.
389+ *
390+ * @param info The ThreadLocalInfo containing usage details.
391+ * @return A string message describing the ThreadLocal's usage.
392+ */
370393 protected abstract String getMessage (ThreadLocalInfo info );
394+ /**
395+ * Determines the mutation type of the ThreadLocal based on its usage info.
396+ * Implementations should define the mutation categories.
397+ * It is used to populate the Data Tables human-readable mutation type column.
398+ *
399+ * @param info The ThreadLocalInfo containing usage details.
400+ * @return A string representing the mutation type.
401+ */
371402 protected abstract String getMutationType (ThreadLocalInfo info );
372403
373404 protected static boolean isThreadLocalType (@ Nullable JavaType type ) {
0 commit comments