diff --git a/src/main/java/org/openrewrite/java/migrate/util/UseMapOf.java b/src/main/java/org/openrewrite/java/migrate/util/UseMapOf.java index 6b2e469307..ee22782db2 100644 --- a/src/main/java/org/openrewrite/java/migrate/util/UseMapOf.java +++ b/src/main/java/org/openrewrite/java/migrate/util/UseMapOf.java @@ -111,6 +111,12 @@ public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { TypeUtils.isOfClassType(n.getClazz() != null ? n.getClazz().getType() : null, "java.util.HashMap")) { Statement statement = body.getStatements().get(0); if (statement instanceof J.Block) { + // Skip when the result is assigned to a concrete `HashMap` declared type rather + // than `Map`: the immutable `Map.of(..)` is not assignable to `HashMap`, and + // `HashMap`-only methods may be invoked on the variable later (issue #1148). + if (isAssignedToConcreteHashMap()) { + return n; + } List putStatements = ((J.Block) statement).getStatements(); List args = new ArrayList<>(); boolean useEntries = putStatements.size() > 10; @@ -194,6 +200,19 @@ private J reattachPairPrefixes(J applied, List puts, boolean return nc.withArguments(Collections.singletonList(mapCall.withArguments(withPrefixes))); } + /** + * Returns {@code true} when the {@code new HashMap<>() {{ ... }}} currently being visited is + * the initializer of a variable whose declared type is the concrete {@code java.util.HashMap} + * rather than the {@code Map} interface. In that case the immutable {@code Map.of(..)} result + * would not be assignable, and {@code HashMap}-specific methods may be used later, so the + * rewrite is skipped (issue #1148). + */ + private boolean isAssignedToConcreteHashMap() { + Object parent = getCursor().getParentTreeCursor().getValue(); + return parent instanceof J.VariableDeclarations.NamedVariable && + TypeUtils.isOfClassType(((J.VariableDeclarations.NamedVariable) parent).getType(), "java.util.HashMap"); + } + @Override public J visitBlock(J.Block block, ExecutionContext ctx) { Map> rewrites = new HashMap<>(); diff --git a/src/test/java/org/openrewrite/java/migrate/util/UseMapOfTest.java b/src/test/java/org/openrewrite/java/migrate/util/UseMapOfTest.java index 5521eb965f..d07ba37072 100644 --- a/src/test/java/org/openrewrite/java/migrate/util/UseMapOfTest.java +++ b/src/test/java/org/openrewrite/java/migrate/util/UseMapOfTest.java @@ -205,6 +205,48 @@ class Test { ); } + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/1148") + @Test + void doNotChangeConcreteHashMapField() { + //language=java + rewriteRun( + spec -> spec.allSources(s -> s.markers(javaVersion(25))), + java( + """ + import java.util.HashMap; + + class Main { + private static final HashMap VALUES = new HashMap() {{ + put("key", "value"); + }}; + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/1148") + @Test + void doNotChangeConcreteHashMapLocalVariable() { + //language=java + rewriteRun( + java( + """ + import java.util.HashMap; + + class Main { + void m() { + HashMap ages = new HashMap<>() {{ + put("Bob", 42); + put("alice", 30); + }}; + } + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/1112") @Test void doNotChangeLinkedHashMap() {