Skip to content
Closed
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.search.threadlocal;

import lombok.EqualsAndHashCode;
import lombok.Value;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Value
@EqualsAndHashCode(callSuper = false)
public class FindNeverMutatedThreadLocals extends AbstractFindThreadLocals {

@Override
public String getDisplayName() {
return "Find ThreadLocal variables that are never mutated";
}

@Override
public String getDescription() {
return "Find `ThreadLocal` variables that are never mutated after initialization. " +
"These are prime candidates for migration to `ScopedValue` in Java 25+ as they are effectively immutable. " +
"The recipe identifies `ThreadLocal` variables that are only initialized but never reassigned or modified through `set()` or `remove()` methods.";
}

@Override
public Set<String> getTags() {
return new HashSet<>(Arrays.asList("java25", "threadlocal", "scopedvalue", "migration"));
}

@Override
protected boolean shouldMarkThreadLocal(ThreadLocalInfo info) {
// Mark ThreadLocals that have no mutations at all
return info.hasNoMutation();
}

@Override
protected String getMessage(ThreadLocalInfo info) {
return "ThreadLocal is never mutated and could be replaced with ScopedValue";
}

@Override
protected String getMutationType(ThreadLocalInfo info) {
return "Never mutated";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.search.threadlocal;

import lombok.EqualsAndHashCode;
import lombok.Value;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Value
@EqualsAndHashCode(callSuper = false)
public class FindThreadLocalsMutableFromOutside extends AbstractFindThreadLocals {

@Override
public String getDisplayName() {
return "Find ThreadLocal variables mutable from outside their defining class";
}

@Override
public String getDescription() {
//language=markdown
return "Find `ThreadLocal` variables that can be mutated from outside their defining class. " +
"These ThreadLocals have the highest risk as they can be modified by any code with access to them. " +
"This includes non-private ThreadLocals or those mutated from other classes in the codebase.";
}

@Override
public Set<String> getTags() {
return new HashSet<>(Arrays.asList("java25", "threadlocal", "scopedvalue", "migration", "security"));
}

@Override
protected boolean shouldMarkThreadLocal(ThreadLocalInfo info) {
// Mark ThreadLocals that are either:
// 1. Actually mutated from outside their defining class
// 2. Non-private (and thus potentially mutable from outside)
return info.hasExternalMutations() || !info.isPrivate();
}

@Override
protected String getMessage(ThreadLocalInfo info) {
if (info.hasExternalMutations()) {
return "ThreadLocal is mutated from outside its defining class";
}

// Non-private but not currently mutated externally
String access = info.isStatic() ? "static " : "";
return "ThreadLocal is " + access + "non-private and can potentially be mutated from outside";
}

@Override
protected String getMutationType(ThreadLocalInfo info) {
if (info.hasExternalMutations()) {
return "Mutated externally";
}

return "Potentially mutable";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.search.threadlocal;

import lombok.EqualsAndHashCode;
import lombok.Value;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Value
@EqualsAndHashCode(callSuper = false)
public class FindThreadLocalsMutatedOnlyInDefiningScope extends AbstractFindThreadLocals {

@Override
public String getDisplayName() {
return "Find ThreadLocal variables mutated only in their defining scope";
}

@Override
public String getDescription() {
//language=markdown
return "Find `ThreadLocal` variables that are only mutated within their defining class or initialization context (constructor/static initializer). " +
"These may be candidates for refactoring as they have limited mutation scope. " +
"The recipe identifies `ThreadLocal` variables that are only modified during initialization or within their declaring class.";
}

@Override
public Set<String> getTags() {
return new HashSet<>(Arrays.asList("java25", "threadlocal", "scopedvalue", "migration"));
}

@Override
protected boolean shouldMarkThreadLocal(ThreadLocalInfo info) {
if (info.hasNoMutation()) {
return false;
}
if (!info.isPrivate()) {
return false;
}
return info.isOnlyLocallyMutated();
}

@Override
protected String getMessage(ThreadLocalInfo info) {
if (info.hasOnlyInitMutations()) {
return "ThreadLocal is only mutated during initialization (constructor/static initializer)";
}

return "ThreadLocal is only mutated within its defining class";
}

@Override
protected String getMutationType(ThreadLocalInfo info) {
if (info.hasOnlyInitMutations()) {
return "Mutated only in initialization";
}

return "Mutated in defining class";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NullMarked
@NonNullFields
package org.openrewrite.java.migrate.search.threadlocal;

import org.jspecify.annotations.NullMarked;
import org.openrewrite.internal.lang.NonNullFields;
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.migrate.table;

import lombok.Value;
import org.openrewrite.Column;
import org.openrewrite.DataTable;
import org.openrewrite.Recipe;

public class ThreadLocalTable extends DataTable<ThreadLocalTable.Row> {

public ThreadLocalTable(Recipe recipe) {
super(recipe,
"ThreadLocal usage",
"ThreadLocal variables and their mutation patterns.");
}

@Value
public static class Row {
@Column(displayName = "Source file",
description = "The source file containing the ThreadLocal declaration.")
String sourceFile;

@Column(displayName = "Class name",
description = "The fully qualified class name where the ThreadLocal is declared.")
String className;

@Column(displayName = "Field name",
description = "The name of the ThreadLocal field.")
String fieldName;

@Column(displayName = "Access modifier",
description = "The access modifier of the ThreadLocal field (private, protected, public, package-private).")
String accessModifier;

@Column(displayName = "Field modifiers",
description = "Additional modifiers like static, final.")
String modifiers;

@Column(displayName = "Mutation type",
description = "Type of mutation detected (Never mutated, Mutated only in initialization, Mutated in defining class, Mutated externally, Potentially mutable).")
String mutationType;

@Column(displayName = "Message",
description = "Detailed message about the ThreadLocal's usage pattern.")
String message;
}
}
Loading