27
27
import java .util .*;
28
28
import java .util .concurrent .atomic .AtomicBoolean ;
29
29
30
- import static java .util .Collections .emptyMap ;
30
+ import static java .util .Collections .*;
31
+ import static org .openrewrite .java .tree .Space .EMPTY ;
31
32
32
33
@ Incubating (since = "7.25.0" )
33
34
public class CombineSemanticallyEqualCatchBlocks extends Recipe {
@@ -86,13 +87,16 @@ public J visitTry(J.Try tryable, ExecutionContext ctx) {
86
87
for (int j = i + 1 ; j < catches .size (); j ++) {
87
88
J .Try .Catch to = catches .get (j );
88
89
// Both 'from' and 'to' may be multi-catches.
89
- for (J .Identifier fromIdentifier : getCaughtExceptions (from )) {
90
- for (J .Identifier toIdentifier : getCaughtExceptions (to )) {
91
- if (fromIdentifier .getType () != null && toIdentifier .getType () != null &&
92
- TypeUtils .isAssignableTo (toIdentifier .getType (), fromIdentifier .getType ())) {
90
+ for (NameTree fromException : getCaughtExceptions (from )) {
91
+ for (NameTree toException : getCaughtExceptions (to )) {
92
+ JavaType fromType = TypeUtils .asFullyQualified (fromException .getType ());
93
+ JavaType toType = TypeUtils .asFullyQualified (toException .getType ());
94
+ if (fromType != null && toType != null && TypeUtils .isAssignableTo (toType , fromType )) {
93
95
Map <J .Try .Catch , Set <J .Identifier >> subTypesMap = parentChildClassRelationship .computeIfAbsent (from , key -> new HashMap <>());
94
96
Set <J .Identifier > childClassIdentifiers = subTypesMap .computeIfAbsent (to , key -> new HashSet <>());
95
- childClassIdentifiers .add (fromIdentifier );
97
+ if (fromException instanceof J .Identifier ) {
98
+ childClassIdentifiers .add ((J .Identifier ) fromException );
99
+ }
96
100
}
97
101
}
98
102
}
@@ -200,12 +204,12 @@ public J visitMultiCatch(J.MultiCatch multiCatch, ExecutionContext ctx) {
200
204
@ Override
201
205
public J visitCatch (J .Try .Catch _catch , ExecutionContext ctx ) {
202
206
J .Try .Catch c = (J .Try .Catch ) super .visitCatch (_catch , ctx );
203
- if (c == scope && !isMultiCatch ( c )) {
207
+ if (c == scope && !( c . getParameter (). getTree (). getTypeExpression () instanceof J . MultiCatch )) {
204
208
if (c .getParameter ().getTree ().getTypeExpression () != null ) {
205
209
List <JRightPadded <NameTree >> combinedCatches = combineEquivalentCatches ();
206
210
c = maybeAutoFormat (c , c .withParameter (c .getParameter ()
207
211
.withTree (c .getParameter ().getTree ()
208
- .withTypeExpression (new J .MultiCatch (Tree .randomId (), Space . EMPTY , Markers .EMPTY , combinedCatches )))),
212
+ .withTypeExpression (new J .MultiCatch (Tree .randomId (), EMPTY , Markers .EMPTY , combinedCatches )))),
209
213
ctx );
210
214
}
211
215
}
@@ -214,62 +218,59 @@ public J visitCatch(J.Try.Catch _catch, ExecutionContext ctx) {
214
218
215
219
private List <JRightPadded <NameTree >> combineEquivalentCatches () {
216
220
Set <J .Identifier > removeIdentifiers = new HashSet <>();
217
-
218
221
List <JRightPadded <NameTree >> combinedCatches = new ArrayList <>();
222
+
219
223
for (J .Try .Catch equivalentCatch : equivalentCatches ) {
220
224
Set <J .Identifier > childClasses = childClassesToExclude .get (equivalentCatch );
221
225
if (childClasses != null ) {
222
226
// Remove child classes that will be unnecessary since the parent exists in the new multi-catch.
223
227
removeIdentifiers .addAll (childClasses );
224
228
}
225
229
226
- // Whitespace works slightly differently between single catches and multi-catches.
227
- // The prefix of each `J.Identifier` is set to `Space.EMPTY` so that auto-format will make all the appropriate changes.
228
- if (isMultiCatch (equivalentCatch )) {
229
- if (equivalentCatch .getParameter ().getTree ().getTypeExpression () != null ) {
230
- J .MultiCatch newMultiCatch = (J .MultiCatch ) equivalentCatch .getParameter ().getTree ().getTypeExpression ();
231
- List <JRightPadded <NameTree >> rightPaddedAlternatives = newMultiCatch .getPadding ().getAlternatives ();
232
- for (JRightPadded <NameTree > alternative : rightPaddedAlternatives ) {
233
- J .Identifier identifier = (J .Identifier ) alternative .getElement ();
234
- identifier = identifier .withPrefix (Space .EMPTY );
235
- alternative = alternative .withElement (identifier );
236
- combinedCatches .add (alternative );
230
+ TypeTree typeExpr = equivalentCatch .getParameter ().getTree ().getTypeExpression ();
231
+ if (typeExpr instanceof J .MultiCatch ) {
232
+ for (JRightPadded <NameTree > alternative : ((J .MultiCatch ) typeExpr ).getPadding ().getAlternatives ()) {
233
+ NameTree name = alternative .getElement ();
234
+ if (name instanceof J .Identifier ) {
235
+ if (removeIdentifiers .contains ((J .Identifier ) name )) {
236
+ continue ; // Skip redundant subtype
237
+ }
238
+ combinedCatches .add (alternative .withElement (((J .Identifier ) name ).withPrefix (EMPTY )));
239
+ } else if (name instanceof J .FieldAccess ) {
240
+ combinedCatches .add (alternative .withElement (((J .FieldAccess ) name ).withPrefix (EMPTY )));
241
+ } else {
242
+ combinedCatches .add (alternative ); // fallback
237
243
}
238
244
}
239
- } else {
240
- if (equivalentCatch .getParameter ().getTree ().getTypeExpression () != null ) {
241
- J .Identifier identifier = ((J .Identifier ) equivalentCatch .getParameter ().getTree ().getTypeExpression ());
242
- identifier = identifier .withPrefix (Space .EMPTY );
243
- JRightPadded <NameTree > rightPadded = JRightPadded .build (identifier );
244
- combinedCatches .add (rightPadded );
245
+ } else if (typeExpr instanceof J .Identifier ) {
246
+ J .Identifier identifier = (J .Identifier ) typeExpr ;
247
+ if (!removeIdentifiers .contains (identifier )) {
248
+ combinedCatches .add (JRightPadded .build (identifier .withPrefix (EMPTY )));
245
249
}
250
+ } else if (typeExpr instanceof J .FieldAccess ) {
251
+ combinedCatches .add (JRightPadded .build (((J .FieldAccess ) typeExpr ).withPrefix (EMPTY )));
246
252
}
247
253
}
248
254
249
- // Add exceptions in `scope` last to filter out exceptions that are children of parent classes
250
- // that were added into the new catch.
251
- if (isMultiCatch (scope )) {
252
- J .MultiCatch multiCatch = (J .MultiCatch ) scope .getParameter ().getTree ().getTypeExpression ();
253
- if (multiCatch != null ) {
254
- List <JRightPadded <NameTree >> alternatives = multiCatch .getPadding ().getAlternatives ();
255
- for (int i = alternatives .size () - 1 ; i >= 0 ; i --) {
256
- if (!removeIdentifiers .contains ((J .Identifier ) alternatives .get (i ).getElement ())) {
257
- JRightPadded <NameTree > alternative = alternatives .get (i );
258
- alternative = alternative .withElement (alternative .getElement ().withPrefix (Space .EMPTY ));
259
- // Preserve the order of the original catches.
260
- combinedCatches .add (0 , alternative );
261
- }
255
+ TypeTree scopeExpr = scope .getParameter ().getTree ().getTypeExpression ();
256
+ if (scopeExpr instanceof J .MultiCatch ) {
257
+ List <JRightPadded <NameTree >> alternatives = ((J .MultiCatch ) scopeExpr ).getPadding ().getAlternatives ();
258
+ for (int i = alternatives .size () - 1 ; i >= 0 ; i --) {
259
+ NameTree name = alternatives .get (i ).getElement ();
260
+ if (name instanceof J .Identifier && !removeIdentifiers .contains (name )) {
261
+ combinedCatches .add (0 , alternatives .get (i ).withElement (((J .Identifier ) name ).withPrefix (EMPTY )));
262
+ } else if (!(name instanceof J .Identifier )) {
263
+ combinedCatches .add (0 , alternatives .get (i ));
262
264
}
263
265
}
264
266
} else {
265
- J .Identifier identifier = (J .Identifier ) scope .getParameter ().getTree ().getTypeExpression ();
266
- if (identifier != null && !removeIdentifiers .contains (identifier )) {
267
- identifier = identifier .withPrefix (Space .EMPTY );
268
- JRightPadded <NameTree > newCatch = JRightPadded .build (identifier );
269
- // Preserve the order of the original catches.
270
- combinedCatches .add (0 , newCatch );
267
+ if (scopeExpr instanceof J .Identifier && !removeIdentifiers .contains (scopeExpr )) {
268
+ combinedCatches .add (0 , JRightPadded .build (((J .Identifier ) scopeExpr ).withPrefix (EMPTY )));
269
+ } else if (scopeExpr instanceof J .FieldAccess ) {
270
+ combinedCatches .add (0 , JRightPadded .build (((J .FieldAccess ) scopeExpr ).withPrefix (EMPTY )));
271
271
}
272
272
}
273
+
273
274
return combinedCatches ;
274
275
}
275
276
}
@@ -285,7 +286,7 @@ private static boolean containSameComments(J.Block body1, J.Block body2) {
285
286
* This visitor is a slight variation of {@link SemanticallyEqual} that accounts for differences
286
287
* in comments between two trees. The visitor was separated, because comments are not considered
287
288
* a part of semantic equivalence.
288
- *
289
+ * <p>
289
290
* Bug fixes related to semantic equality that are found by {@link CombineSemanticallyEqualCatchBlocks}
290
291
* should be applied to {@link SemanticallyEqual} too.
291
292
*/
@@ -436,8 +437,8 @@ public J.ArrayType visitArrayType(J.ArrayType arrayType, J j) {
436
437
J .ArrayType compareTo = (J .ArrayType ) j ;
437
438
if (!TypeUtils .isOfType (arrayType .getType (), compareTo .getType ()) ||
438
439
doesNotContainSameComments (arrayType .getPrefix (), compareTo .getPrefix ()) ||
439
- nullMissMatch (arrayType .getAnnotations (), compareTo .getAnnotations ()) ||
440
- arrayType .getAnnotations ().size () != compareTo .getAnnotations ().size ()) {
440
+ nullMissMatch (arrayType .getAnnotations (), compareTo .getAnnotations ()) ||
441
+ arrayType .getAnnotations ().size () != compareTo .getAnnotations ().size ()) {
441
442
isEqual .set (false );
442
443
return arrayType ;
443
444
}
@@ -1795,32 +1796,14 @@ public <N extends NameTree> N visitTypeName(N firstTypeName, J j) {
1795
1796
/**
1796
1797
* Collection the caught exceptions from a {@link J.Try.Catch}.
1797
1798
*/
1798
- private static Set <J .Identifier > getCaughtExceptions (J .Try .Catch aCatch ) {
1799
- Set <J .Identifier > caughtExceptions = new HashSet <>();
1800
- if (isMultiCatch (aCatch )) {
1801
- J .MultiCatch multiCatch = (J .MultiCatch ) aCatch .getParameter ().getTree ().getTypeExpression ();
1802
- if (multiCatch != null ) {
1803
- for (NameTree alternative : multiCatch .getAlternatives ()) {
1804
- J .Identifier identifier = (J .Identifier ) alternative ;
1805
- caughtExceptions .add (identifier );
1806
- }
1807
- }
1808
- } else {
1809
- J .Identifier identifier = (J .Identifier ) aCatch .getParameter ().getTree ().getTypeExpression ();
1810
- if (identifier != null ) {
1811
- caughtExceptions .add (identifier );
1812
- }
1813
- }
1814
- return caughtExceptions ;
1815
- }
1816
-
1817
- /**
1818
- * Returns true of a {@link J.Try.Catch} is a {@link J.MultiCatch}.
1819
- * Note: A null type expression will produce a false negative, but the recipe will not
1820
- * change catches with a null type.
1821
- */
1822
- private static boolean isMultiCatch (J .Try .Catch aCatch ) {
1823
- return aCatch .getParameter ().getTree ().getTypeExpression () instanceof J .MultiCatch ;
1799
+ private static List <NameTree > getCaughtExceptions (J .Try .Catch aCatch ) {
1800
+ TypeTree typeExpr = aCatch .getParameter ().getTree ().getTypeExpression ();
1801
+ if (typeExpr instanceof J .MultiCatch ) {
1802
+ return ((J .MultiCatch ) typeExpr ).getAlternatives ();
1803
+ } else if (typeExpr != null ) { // Can be J.Identifier or J.FieldAccess
1804
+ return singletonList (typeExpr );
1805
+ }
1806
+ return emptyList ();
1824
1807
}
1825
1808
}
1826
1809
}
0 commit comments