Skip to content

Commit 95772cd

Browse files
timtebeekclaude
andauthored
Add SimplifyJacksonExceptionCatch recipe (#63)
* Add SimplifyJacksonExceptionCatch recipe In Jackson 3, JacksonException and its subtypes extend RuntimeException. This recipe simplifies multi-catch clauses by removing Jackson exception types when RuntimeException is also caught, since catching both is redundant. For example: catch (JacksonException | RuntimeException e) becomes: catch (RuntimeException e) Handles all Jackson 3 exception types including JacksonException, StreamReadException, StreamWriteException, UnexpectedEndOfInputException, and DatabindException. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Replace stubs with test classpathFromResources * Simplify implementation * Minimize further --------- Co-authored-by: Claude <[email protected]>
1 parent 9685c9a commit 95772cd

File tree

5 files changed

+293
-0
lines changed

5 files changed

+293
-0
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,5 @@ dependencies {
4646
recipeDependencies {
4747
parserClasspath("com.fasterxml.jackson.core:jackson-annotations:2.19.2")
4848
parserClasspath("com.fasterxml.jackson.core:jackson-core:2.19.2")
49+
testParserClasspath("tools.jackson.core:jackson-core:3.+")
4950
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
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.jackson;
17+
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Preconditions;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.internal.ListUtils;
23+
import org.openrewrite.java.JavaIsoVisitor;
24+
import org.openrewrite.java.search.UsesType;
25+
import org.openrewrite.java.tree.J;
26+
import org.openrewrite.java.tree.NameTree;
27+
import org.openrewrite.java.tree.TypeUtils;
28+
29+
import java.util.List;
30+
import java.util.Set;
31+
32+
import static java.util.Collections.singleton;
33+
34+
public class SimplifyJacksonExceptionCatch extends Recipe {
35+
36+
private static final String RUNTIME_EXCEPTION = "java.lang.RuntimeException";
37+
private static final String JACKSON_RUNTIME_EXCEPTION = "tools.jackson.core.JacksonException";
38+
39+
@Override
40+
public String getDisplayName() {
41+
return "Simplify catch clauses for Jackson exceptions";
42+
}
43+
44+
@Override
45+
public String getDescription() {
46+
return "In Jackson 3, `JacksonException` and its subtypes extend `RuntimeException`. " +
47+
"This recipe simplifies multi-catch clauses by removing Jackson exception types " +
48+
"when `RuntimeException` is also caught, since catching both is redundant. " +
49+
"For example, `catch (JacksonException | RuntimeException e)` becomes `catch (RuntimeException e)`.";
50+
}
51+
52+
@Override
53+
public Set<String> getTags() {
54+
return singleton("jackson-3");
55+
}
56+
57+
@Override
58+
public TreeVisitor<?, ExecutionContext> getVisitor() {
59+
return Preconditions.check(
60+
new UsesType<>("tools.jackson.core.JacksonException", true),
61+
new JavaIsoVisitor<ExecutionContext>() {
62+
@Override
63+
public J.MultiCatch visitMultiCatch(J.MultiCatch multiCatch, ExecutionContext ctx) {
64+
J.MultiCatch mc = super.visitMultiCatch(multiCatch, ctx);
65+
if (mc.getAlternatives().stream().noneMatch(nt -> TypeUtils.isOfClassType(nt.getType(), RUNTIME_EXCEPTION))) {
66+
return mc;
67+
}
68+
69+
List<NameTree> filtered = ListUtils.filter(mc.getAlternatives(), nt -> {
70+
if (TypeUtils.isAssignableTo(JACKSON_RUNTIME_EXCEPTION, nt.getType())) {
71+
maybeRemoveImport(TypeUtils.asFullyQualified(nt.getType()));
72+
return false;
73+
}
74+
return true;
75+
});
76+
return mc.withAlternatives(ListUtils.mapFirst(filtered, first -> first.withPrefix(mc.getAlternatives().get(0).getPrefix())));
77+
}
78+
}
79+
);
80+
}
81+
}

src/main/resources/META-INF/rewrite/jackson-2-3.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ recipeList:
3636
- org.openrewrite.java.jackson.UseModernDateTimeSerialization
3737
- org.openrewrite.java.jackson.ReplaceStreamWriteCapability
3838
- org.openrewrite.java.jackson.UpgradeJackson_2_3_PackageChanges
39+
- org.openrewrite.java.jackson.SimplifyJacksonExceptionCatch
3940

4041
---
4142
type: specs.openrewrite.org/v1beta/recipe
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
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.jackson;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.openrewrite.DocumentExample;
20+
import org.openrewrite.InMemoryExecutionContext;
21+
import org.openrewrite.java.JavaParser;
22+
import org.openrewrite.test.RecipeSpec;
23+
import org.openrewrite.test.RewriteTest;
24+
25+
import static org.openrewrite.java.Assertions.java;
26+
27+
class SimplifyJacksonExceptionCatchTest implements RewriteTest {
28+
29+
@Override
30+
public void defaults(RecipeSpec spec) {
31+
spec.recipe(new SimplifyJacksonExceptionCatch())
32+
.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(),
33+
"jackson-core-3"));
34+
}
35+
36+
@DocumentExample
37+
@Test
38+
void simplifiesJacksonExceptionWithRuntimeException() {
39+
rewriteRun(
40+
java(
41+
"""
42+
import tools.jackson.core.JacksonException;
43+
44+
class Test {
45+
void doSomething() {
46+
try {
47+
// some code
48+
} catch (JacksonException | RuntimeException e) {
49+
e.printStackTrace();
50+
}
51+
}
52+
}
53+
""",
54+
"""
55+
class Test {
56+
void doSomething() {
57+
try {
58+
// some code
59+
} catch (RuntimeException e) {
60+
e.printStackTrace();
61+
}
62+
}
63+
}
64+
"""
65+
)
66+
);
67+
}
68+
69+
@Test
70+
void simplifiesStreamReadException() {
71+
rewriteRun(
72+
java(
73+
"""
74+
import tools.jackson.core.exc.StreamReadException;
75+
76+
class Test {
77+
void doSomething() {
78+
try {
79+
// some code
80+
} catch (StreamReadException | RuntimeException e) {
81+
e.printStackTrace();
82+
}
83+
}
84+
}
85+
""",
86+
"""
87+
class Test {
88+
void doSomething() {
89+
try {
90+
// some code
91+
} catch (RuntimeException e) {
92+
e.printStackTrace();
93+
}
94+
}
95+
}
96+
"""
97+
)
98+
);
99+
}
100+
101+
@Test
102+
void simplifiesMultipleJacksonExceptions() {
103+
rewriteRun(
104+
java(
105+
"""
106+
import tools.jackson.core.exc.StreamReadException;
107+
import tools.jackson.core.exc.StreamWriteException;
108+
109+
class Test {
110+
void doSomething() {
111+
try {
112+
// some code
113+
} catch (StreamReadException | StreamWriteException | RuntimeException e) {
114+
e.printStackTrace();
115+
}
116+
}
117+
}
118+
""",
119+
"""
120+
class Test {
121+
void doSomething() {
122+
try {
123+
// some code
124+
} catch (RuntimeException e) {
125+
e.printStackTrace();
126+
}
127+
}
128+
}
129+
"""
130+
)
131+
);
132+
}
133+
134+
@Test
135+
void preservesOtherExceptionsInMultiCatch() {
136+
rewriteRun(
137+
java(
138+
"""
139+
import tools.jackson.core.JacksonException;
140+
import java.io.IOException;
141+
142+
class Test {
143+
void doSomething() {
144+
try {
145+
// some code
146+
} catch (IOException | JacksonException | RuntimeException e) {
147+
e.printStackTrace();
148+
}
149+
}
150+
}
151+
""",
152+
"""
153+
import java.io.IOException;
154+
155+
class Test {
156+
void doSomething() {
157+
try {
158+
// some code
159+
} catch (IOException | RuntimeException e) {
160+
e.printStackTrace();
161+
}
162+
}
163+
}
164+
"""
165+
)
166+
);
167+
}
168+
169+
@Test
170+
void noChangeForSingleExceptionCatch() {
171+
rewriteRun(
172+
java(
173+
"""
174+
import tools.jackson.core.JacksonException;
175+
176+
class Test {
177+
void doSomething() {
178+
try {
179+
// some code
180+
} catch (JacksonException e) {
181+
e.printStackTrace();
182+
}
183+
}
184+
}
185+
"""
186+
)
187+
);
188+
}
189+
190+
@Test
191+
void noChangeWhenNoJacksonException() {
192+
rewriteRun(
193+
java(
194+
"""
195+
import java.io.IOException;
196+
197+
class Test {
198+
void doSomething() {
199+
try {
200+
// some code
201+
} catch (IOException | RuntimeException e) {
202+
e.printStackTrace();
203+
}
204+
}
205+
}
206+
"""
207+
)
208+
);
209+
}
210+
}
40.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)