diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java index 39ca3ed049e..5419f7c5be6 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java @@ -32,6 +32,7 @@ import static org.openrewrite.java.Assertions.java; import static org.openrewrite.properties.Assertions.properties; import static org.openrewrite.xml.Assertions.xml; +import static org.openrewrite.yaml.Assertions.yaml; @SuppressWarnings("ConstantConditions") class ChangePackageTest implements RewriteTest { @@ -430,11 +431,11 @@ class A { } """, """ - import org.openrewrite.test.other.Test; - class A { - Test test = null; - } - """, + import org.openrewrite.test.other.Test; + class A { + Test test = null; + } + """, spec -> spec.afterRecipe(cu -> { assertThat(cu.findType("org.openrewrite.other.Test")).isEmpty(); assertThat(cu.findType("org.openrewrite.test.other.Test")).isNotEmpty(); @@ -488,11 +489,11 @@ class A { } """, """ - import org.openrewrite.test.other.Test; - class A { - Test test = null; - } - """, + import org.openrewrite.test.other.Test; + class A { + Test test = null; + } + """, spec -> spec.afterRecipe(cu -> { assertThat(cu.findType("org.openrewrite.other.Test")).isEmpty(); assertThat(cu.findType("org.openrewrite.test.other.Test")).isNotEmpty(); @@ -546,11 +547,11 @@ class A { } """, """ - import org.openrewrite.test.other.Test; - class A { - Test test = null; - } - """, + import org.openrewrite.test.other.Test; + class A { + Test test = null; + } + """, spec -> spec.afterRecipe(cu -> { assertThat(cu.findType("org.openrewrite.other.Test")).isEmpty(); assertThat(cu.findType("org.openrewrite.test.other.Test")).isNotEmpty(); @@ -705,7 +706,7 @@ void method() {} void annotationArgument() { rewriteRun( java( - """ + """ package org.openrewrite; public class Argument {} """, @@ -757,7 +758,7 @@ void method() {} void annotationArgumentNamed() { rewriteRun( java( - """ + """ package org.openrewrite; public class Argument {} """, @@ -807,7 +808,7 @@ void method() {} void annotationArgumentFullyQualified() { rewriteRun( java( - """ + """ package org.openrewrite; public class Argument {} """, @@ -855,7 +856,7 @@ void method() {} void annotationArgumentNamedFullyQualified() { rewriteRun( java( - """ + """ package org.openrewrite; public class Argument {} """, @@ -1439,7 +1440,7 @@ void staticImport() { java( """ import static org.openrewrite.Test.stat; - + public class B { public void test() { stat(); @@ -1448,7 +1449,7 @@ public void test() { """, """ import static org.openrewrite.test.Test.stat; - + public class B { public void test() { stat(); @@ -1724,7 +1725,6 @@ void changePackageInSpringXml() { """ ) ); - } @Test @@ -1741,7 +1741,30 @@ void changeTypeInPropertiesFile() { a.property=java.cool.String b.property=java.cool.test.String c.property=String - """, spec -> spec.path("application.properties")) + """, + spec -> spec.path("application.properties")) + ); + } + + @Test + void changePackageInYaml() { + rewriteRun( + spec -> spec.recipe(new ChangePackage("java.lang", "java.cool", true)), + yaml( + """ + root: + a: java.lang.String + b: java.lang.test.String + c: String + """, + """ + root: + a: java.cool.String + b: java.cool.test.String + c: String + """, + spec -> spec.path("application.yaml") + ) ); } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java index 487e56e4a8e..ad20be88dc7 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java @@ -31,6 +31,7 @@ import static org.openrewrite.java.Assertions.java; import static org.openrewrite.properties.Assertions.properties; import static org.openrewrite.xml.Assertions.xml; +import static org.openrewrite.yaml.Assertions.yaml; @SuppressWarnings("ConstantConditions") class ChangeTypeTest implements RewriteTest { @@ -2079,4 +2080,28 @@ void changeTypeInPropertiesFile() { """, spec -> spec.path("application.properties")) ); } + + @Test + void changeTypeInYaml() { + rewriteRun( + spec -> spec.recipe(new ChangeType("java.lang.String", "java.lang.Integer", true)), + yaml( + """ + root: + a: java.lang.String + b: java.lang.StringBuilder + c: java.lang.test.String + d: String + """, + """ + root: + a: java.lang.Integer + b: java.lang.StringBuilder + c: java.lang.test.String + d: String + """, + spec -> spec.path("application.yaml") + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java b/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java index 1cf2f2ff53f..1a1b5aa6f40 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java @@ -119,5 +119,4 @@ public boolean matchesReference(Reference reference) { public Reference.Renamer createRenamer(String newName) { return reference -> newName; } - } diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java b/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java index 038aafccfd6..2d5fd55846a 100644 --- a/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java +++ b/rewrite-properties/src/main/java/org/openrewrite/properties/trait/PropertiesReference.java @@ -53,9 +53,6 @@ public boolean supportsRename() { return true; } - /** - * {@inheritDoc} - */ @Override public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { Tree tree = cursor.getValue(); @@ -67,7 +64,7 @@ public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { return tree; } - public static class Matcher extends SimpleTraitMatcher { + private static class Matcher extends SimpleTraitMatcher { private static final Predicate javaFullyQualifiedTypeMatcher = Pattern.compile( "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(?:\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*").asPredicate(); @@ -75,7 +72,7 @@ public static class Matcher extends SimpleTraitMatcher { protected @Nullable PropertiesReference test(Cursor cursor) { Object value = cursor.getValue(); if (value instanceof Properties.Entry && - javaFullyQualifiedTypeMatcher.test(((Properties.Entry) value).getValue().getText())) { + javaFullyQualifiedTypeMatcher.test(((Properties.Entry) value).getValue().getText())) { return new PropertiesReference(cursor, determineKind(((Properties.Entry) value).getValue().getText())); } return null; @@ -88,8 +85,14 @@ private Kind determineKind(String value) { @SuppressWarnings("unused") public static class Provider implements Reference.Provider { + private static final Predicate applicationPropertiesMatcher = Pattern.compile("^application(-\\w+)?\\.properties$").asPredicate(); + @Override + public boolean isAcceptable(SourceFile sourceFile) { + return sourceFile instanceof Properties.File && applicationPropertiesMatcher.test(sourceFile.getSourcePath().getFileName().toString()); + } + @Override public Set getReferences(SourceFile sourceFile) { Set references = new HashSet<>(); @@ -99,10 +102,5 @@ public Set getReferences(SourceFile sourceFile) { }).visit(sourceFile, 0); return references; } - - @Override - public boolean isAcceptable(SourceFile sourceFile) { - return sourceFile instanceof Properties.File && applicationPropertiesMatcher.test(sourceFile.getSourcePath().getFileName().toString()); - } } } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java new file mode 100644 index 00000000000..23705fdae96 --- /dev/null +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/YamlReference.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.yaml.trait; + +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.trait.Reference; +import org.openrewrite.trait.SimpleTraitMatcher; +import org.openrewrite.yaml.tree.Yaml; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +@Value +public class YamlReference implements Reference { + Cursor cursor; + Kind kind; + + @Override + public Kind getKind() { + return kind; + } + + @Override + public String getValue() { + if (getTree() instanceof Yaml.Scalar) { + return ((Yaml.Scalar) getTree()).getValue(); + } + throw new IllegalArgumentException("getTree() must be an Yaml.Scalar: " + getTree().getClass()); + } + + @Override + public boolean supportsRename() { + return true; + } + + @Override + public Tree rename(Renamer renamer, Cursor cursor, ExecutionContext ctx) { + Tree tree = cursor.getValue(); + if (tree instanceof Yaml.Scalar) { + return ((Yaml.Scalar) tree).withValue(renamer.rename(this)); + } + throw new IllegalArgumentException("cursor.getValue() must be an Yaml.Scalar but is: " + tree.getClass()); + } + + private static class Matcher extends SimpleTraitMatcher { + private static final Predicate javaFullyQualifiedTypePattern = Pattern.compile( + "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(?:\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*") + .asPredicate(); + + @Override + protected @Nullable YamlReference test(Cursor cursor) { + Object value = cursor.getValue(); + if (value instanceof Yaml.Scalar && + javaFullyQualifiedTypePattern.test(((Yaml.Scalar) value).getValue())) { + return new YamlReference(cursor, determineKind(((Yaml.Scalar) value).getValue())); + } + return null; + } + + private Kind determineKind(String value) { + return Character.isUpperCase(value.charAt(value.lastIndexOf('.') + 1)) ? Kind.TYPE : Kind.PACKAGE; + } + } + + @SuppressWarnings("unused") + public static class Provider implements Reference.Provider { + + private static final Predicate applicationPropertiesMatcher = Pattern.compile("^application(-\\w+)?\\.(yaml|yml)$").asPredicate(); + + @Override + public boolean isAcceptable(SourceFile sourceFile) { + return sourceFile instanceof Yaml.Documents && applicationPropertiesMatcher.test(sourceFile.getSourcePath().getFileName().toString()); + } + + @Override + public Set getReferences(SourceFile sourceFile) { + Set references = new HashSet<>(); + new Matcher().asVisitor(reference -> { + references.add(reference); + return reference.getTree(); + }).visit(sourceFile, 0); + return references; + } + } +} diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/package-info.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/package-info.java new file mode 100644 index 00000000000..b2cf0ce8763 --- /dev/null +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/trait/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.yaml.trait; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java index 5245d9ab397..4639bfbf40b 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/tree/Yaml.java @@ -17,12 +17,15 @@ import lombok.*; import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.marker.Markers; import org.openrewrite.yaml.YamlVisitor; import org.openrewrite.yaml.internal.YamlPrinter; +import java.beans.Transient; +import java.lang.ref.SoftReference; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; @@ -60,8 +63,10 @@ default

boolean isAcceptable(TreeVisitor v, P p) { @Value @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @RequiredArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) @With - class Documents implements Yaml, SourceFile { + class Documents implements Yaml, SourceFileWithReferences { @EqualsAndHashCode.Include UUID id; @@ -125,6 +130,27 @@ public Documents withPrefix(String prefix) { public

TreeVisitor> printer(Cursor cursor) { return new YamlPrinter<>(); } + + @Nullable + @NonFinal + transient SoftReference references; + + @Transient + @Override + public References getReferences() { + References cache; + if (this.references == null) { + cache = References.build(this); + this.references = new SoftReference<>(cache); + } else { + cache = this.references.get(); + if (cache == null || cache.getSourceFile() != this) { + cache = References.build(this); + this.references = new SoftReference<>(cache); + } + } + return cache; + } } @Value diff --git a/rewrite-yaml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider b/rewrite-yaml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider new file mode 100644 index 00000000000..dd857df5564 --- /dev/null +++ b/rewrite-yaml/src/main/resources/META-INF/services/org.openrewrite.trait.Reference$Provider @@ -0,0 +1 @@ +org.openrewrite.yaml.trait.YamlReference$Provider \ No newline at end of file diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/YamlReferenceTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/YamlReferenceTest.java new file mode 100644 index 00000000000..a9bd71b35f4 --- /dev/null +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/trait/YamlReferenceTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.yaml.trait; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.trait.Reference; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.yaml.Assertions.yaml; + +class YamlReferenceTest implements RewriteTest { + @Language("yml") + private static final String YAML = """ + root: + a: java.lang.String + b: java.lang + c: String + recipelist: + - org.openrewrite.java.DoSomething: + option: 'org.foo.Bar' + """; + + + @ParameterizedTest + @CsvSource({ + "application.yaml", + "application.yml", + "application-test.yaml", + "application-test.yml", + "/foo/bar/application-test.yaml", + "/foo/bar/application-test.yml", + }) + void findJavaReferencesInYamlProperties(String filename) { + rewriteRun( + yaml( + YAML, + spec -> spec.path(filename).afterRecipe(doc -> { + assertThat(doc.getReferences().getReferences()).satisfiesExactlyInAnyOrder( + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.TYPE); + assertThat(ref.getValue()).isEqualTo("java.lang.String"); + }, + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.PACKAGE); + assertThat(ref.getValue()).isEqualTo("java.lang"); + }, + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.TYPE); + assertThat(ref.getValue()).isEqualTo("org.openrewrite.java.DoSomething"); + }, + ref -> { + assertThat(ref.getKind()).isEqualTo(Reference.Kind.TYPE); + assertThat(ref.getValue()).isEqualTo("org.foo.Bar"); + }); + })) + ); + } + + @ParameterizedTest + @CsvSource({ + "application-.yaml", + "application-.yml", + "application.test.yaml", + "application.test.yml", + "other-application.yaml", + "other-application.yml", + "other.yaml", + "other.yml", + "/foo/bar/other.yaml", + "/foo/bar/other.yml" + }) + void noReferencesInMismatchedFilenames(String filename) { + rewriteRun( + yaml( + YAML, + spec -> spec.path(filename).afterRecipe(doc -> assertThat(doc.getReferences().getReferences()).isEmpty()) + ) + ); + } +}