Skip to content

Commit 6d7f1fb

Browse files
arodionovgithub-actions[bot]timtebeek
authored
Recipes for searching Threads or Executors usage (#722)
* Recipes for searching Threads or Executors usage Creating a recipies to find potential places in a code to be converted to Virtual Threads. * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Minimize exposed recipe surface --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim te Beek <tim@moderne.io>
1 parent e8e9f50 commit 6d7f1fb

2 files changed

Lines changed: 318 additions & 0 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#
2+
# Copyright 2025 the original author or authors.
3+
# <p>
4+
# Licensed under the Moderne Source Available License (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
# <p>
8+
# https://docs.moderne.io/licensing/moderne-source-available-license
9+
# <p>
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
---
18+
type: specs.openrewrite.org/v1beta/recipe
19+
name: org.openrewrite.java.migrate.lang.FindVirtualThreadOpportunities
20+
displayName: Find Virtual Thread opportunities
21+
description: >-
22+
Find opportunities to convert existing code to use Virtual Threads.
23+
tags:
24+
- java21
25+
- virtual_threads
26+
recipeList:
27+
- org.openrewrite.java.search.FindImplementations:
28+
typeName: java.lang.Thread
29+
- org.openrewrite.java.search.FindMethods:
30+
methodPattern: java.lang.Thread#<constructor>(..)
31+
- org.openrewrite.java.migrate.lang.FindNonVirtualExecutors
32+
33+
---
34+
type: specs.openrewrite.org/v1beta/recipe
35+
name: org.openrewrite.java.migrate.lang.FindNonVirtualExecutors
36+
displayName: Find non-virtual `ExecutorService` creation
37+
description: >-
38+
Find all places where static `java.util.concurrent.Executors` method creates
39+
a non-virtual `java.util.concurrent.ExecutorService`. This recipe can be used
40+
to search fro `ExecutorService` that can be replaced by Virtual Thread executor.
41+
tags:
42+
- java21
43+
- virtual_threads
44+
recipeList:
45+
- org.openrewrite.java.search.FindMethods:
46+
methodPattern: java.util.concurrent.Executors#newCachedThreadPool(..)
47+
- org.openrewrite.java.search.FindMethods:
48+
methodPattern: java.util.concurrent.Executors#newFixedThreadPool(..)
49+
- org.openrewrite.java.search.FindMethods:
50+
methodPattern: java.util.concurrent.Executors#newSingleThreadExecutor(..)
51+
- org.openrewrite.java.search.FindMethods:
52+
methodPattern: java.util.concurrent.Executors#newWorkStealingPool(..)
53+
- org.openrewrite.java.search.FindMethods:
54+
methodPattern: java.util.concurrent.Executors#newScheduledThreadPool(..)
55+
- org.openrewrite.java.search.FindMethods:
56+
methodPattern: java.util.concurrent.Executors#newSingleThreadScheduledExecutor(..)
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.lang;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.openrewrite.DocumentExample;
20+
import org.openrewrite.test.RecipeSpec;
21+
import org.openrewrite.test.RewriteTest;
22+
import org.openrewrite.test.SourceSpec;
23+
24+
import static org.openrewrite.java.Assertions.java;
25+
26+
class UseThreadsOrExecutorsTest implements RewriteTest {
27+
28+
@Override
29+
public void defaults(RecipeSpec spec) {
30+
spec.recipeFromResource("/META-INF/rewrite/java-virtual-threads.yml", "org.openrewrite.java.migrate.lang.FindVirtualThreadOpportunities");
31+
}
32+
33+
@DocumentExample
34+
@Test
35+
void findThreadConstructorWithRunnable() {
36+
rewriteRun(
37+
java(
38+
"""
39+
class Test {
40+
Thread t = new Thread(new Runnable() {
41+
@Override
42+
public void run() {}
43+
});
44+
}
45+
""",
46+
"""
47+
class Test {
48+
Thread t = /*~~>*/new Thread(new Runnable() {
49+
@Override
50+
public void run() {}
51+
});
52+
}
53+
"""
54+
)
55+
);
56+
}
57+
58+
@Test
59+
void findThreadConstructorWithClassImplementingRunnable() {
60+
rewriteRun(
61+
java(
62+
"""
63+
class MyRunnable implements Runnable {
64+
@Override
65+
public void run() {}
66+
}
67+
""",
68+
SourceSpec::skip
69+
),
70+
java(
71+
"""
72+
class Test {
73+
Thread t = new Thread(new MyRunnable());
74+
}
75+
""",
76+
"""
77+
class Test {
78+
Thread t = /*~~>*/new Thread(new MyRunnable());
79+
}
80+
"""
81+
)
82+
);
83+
}
84+
85+
@Test
86+
void findThreadConstructorWithLambda() {
87+
rewriteRun(
88+
java(
89+
"""
90+
class Test {
91+
Thread t = new Thread(() -> {});
92+
}
93+
""",
94+
"""
95+
class Test {
96+
Thread t = /*~~>*/new Thread(() -> {});
97+
}
98+
"""
99+
)
100+
);
101+
}
102+
103+
@Test
104+
void findThreadConstructorWithThreadGroupAndRunnable() {
105+
rewriteRun(
106+
java(
107+
"""
108+
class Test {
109+
Thread t = new Thread(new ThreadGroup("ThreadGroup"), new Runnable() {
110+
@Override
111+
public void run() {}
112+
}, "name");
113+
}
114+
""",
115+
"""
116+
class Test {
117+
Thread t = /*~~>*/new Thread(new ThreadGroup("ThreadGroup"), new Runnable() {
118+
@Override
119+
public void run() {}
120+
}, "name");
121+
}
122+
"""
123+
)
124+
);
125+
}
126+
127+
@Test
128+
void findThreadConstructorWithNullThreadGroupAndRunnable() {
129+
rewriteRun(
130+
java(
131+
"""
132+
class Test {
133+
Thread t = new Thread(null, new Runnable() {
134+
@Override
135+
public void run() {}
136+
}, "name");
137+
}
138+
""",
139+
"""
140+
class Test {
141+
Thread t = /*~~>*/new Thread(null, new Runnable() {
142+
@Override
143+
public void run() {}
144+
}, "name");
145+
}
146+
"""
147+
)
148+
);
149+
}
150+
151+
@Test
152+
void findClassExtendsThreadAndOverrideRun() {
153+
rewriteRun(
154+
java(
155+
"""
156+
public class MyThread extends Thread {
157+
public void run() {}
158+
}
159+
""",
160+
"""
161+
/*~~>*/public class MyThread extends Thread {
162+
public void run() {}
163+
}
164+
"""
165+
)
166+
);
167+
}
168+
169+
@Test
170+
void findEmptyThreadConstructor() {
171+
rewriteRun(
172+
java(
173+
"""
174+
class Test {
175+
Thread t = new Thread();
176+
}
177+
""",
178+
"""
179+
class Test {
180+
Thread t = /*~~>*/new Thread();
181+
}
182+
"""
183+
)
184+
);
185+
}
186+
187+
@Test
188+
void findEmptyThreadConstructorNonSuper() {
189+
rewriteRun(
190+
java(
191+
"""
192+
class Test extends Thread {
193+
Thread t = new Test();
194+
195+
Test() {
196+
super();
197+
}
198+
}
199+
""",
200+
"""
201+
/*~~>*/class Test extends Thread {
202+
Thread t = new Test();
203+
204+
Test() {
205+
/*~~>*/super();
206+
}
207+
}
208+
"""
209+
)
210+
);
211+
}
212+
213+
@Test
214+
void findClassExtendsThread() {
215+
rewriteRun(
216+
java(
217+
"""
218+
public class MyThread extends Thread {
219+
}
220+
""",
221+
"""
222+
/*~~>*/public class MyThread extends Thread {
223+
}
224+
"""
225+
)
226+
);
227+
}
228+
229+
@Test
230+
void findExecutors() {
231+
rewriteRun(
232+
java(
233+
"""
234+
import java.util.concurrent.ExecutorService;
235+
import java.util.concurrent.Executors;
236+
237+
class Test {
238+
ExecutorService executorService1 = Executors.newFixedThreadPool(10);
239+
ExecutorService executorService2 = Executors.newCachedThreadPool();
240+
ExecutorService executorService3 = Executors.newSingleThreadExecutor();
241+
ExecutorService executorService4 = Executors.newWorkStealingPool();
242+
ExecutorService executorService5 = Executors.newScheduledThreadPool(1);
243+
ExecutorService executorService6 = Executors.newSingleThreadScheduledExecutor();
244+
}
245+
""",
246+
"""
247+
import java.util.concurrent.ExecutorService;
248+
import java.util.concurrent.Executors;
249+
250+
class Test {
251+
ExecutorService executorService1 = /*~~>*/Executors.newFixedThreadPool(10);
252+
ExecutorService executorService2 = /*~~>*/Executors.newCachedThreadPool();
253+
ExecutorService executorService3 = /*~~>*/Executors.newSingleThreadExecutor();
254+
ExecutorService executorService4 = /*~~>*/Executors.newWorkStealingPool();
255+
ExecutorService executorService5 = /*~~>*/Executors.newScheduledThreadPool(1);
256+
ExecutorService executorService6 = /*~~>*/Executors.newSingleThreadScheduledExecutor();
257+
}
258+
"""
259+
)
260+
);
261+
}
262+
}

0 commit comments

Comments
 (0)