From fbfbee2f30445a00028fe3256d7de0f55c12b107 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 10:25:48 +0100 Subject: [PATCH 1/9] Add recipe generator for InlineMe annotations --- build.gradle.kts | 13 +- .../InlineMethodCallsRecipeGenerator.java | 303 ++++++++++++++++++ 2 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java diff --git a/build.gradle.kts b/build.gradle.kts index 754ebaf..792abef 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,7 @@ val rewriteVersion = rewriteRecipe.rewriteVersion.get() dependencies { implementation(platform("org.openrewrite:rewrite-bom:$rewriteVersion")) - runtimeOnly("org.openrewrite:rewrite-java") + implementation("org.openrewrite:rewrite-java") runtimeOnly("org.openrewrite:rewrite-templating:${rewriteVersion}") runtimeOnly("ai.timefold.solver:timefold-solver-migration:latest.release") { @@ -104,4 +104,15 @@ tasks { mainClass = "org.openrewrite.recipe.quarkus.internal.AggregateQuarkusUpdates" classpath = sourceSets.getByName("test").runtimeClasspath } + val generateInlineGuavaMethods by registering(JavaExec::class) { + group = "generate" + description = "Generate Quarkus migration aggregation Recipes." + mainClass = "org.openrewrite.java.internal.parser.InlineMethodCallsRecipeGenerator" + classpath = sourceSets.getByName("test").runtimeClasspath + args( + "src/main/resources/META-INF/rewrite/classpath.tsv.gz", + "src/main/resources/META-INF/rewrite/inline-guava-methods.yml", + "com.google.guava.InlineGuavaMethods" + ) + } } diff --git a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java new file mode 100644 index 0000000..d7585e4 --- /dev/null +++ b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java @@ -0,0 +1,303 @@ +/* + * Copyright 2025 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.java.internal.parser; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.InMemoryExecutionContext; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Year; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import java.util.zip.GZIPInputStream; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.groupingBy; + +public class InlineMethodCallsRecipeGenerator { + + public static void main(String[] args) { + if (args.length < 3) { + System.err.println("Usage: InlineMethodCallsRecipeGenerator "); + System.exit(1); + } + + Path inputPath = Paths.get(args[0]); + Path outputPath = Paths.get(args[1]); + String recipeName = args[2]; + + generate(inputPath, outputPath, recipeName); + } + + static void generate(Path tsvFile, Path outputPath, String recipeName) { + List inlineMethods = new ArrayList<>(); + + TypeTable.Reader reader = new TypeTable.Reader(new InMemoryExecutionContext()); + try (InputStream is = Files.newInputStream(tsvFile); InputStream inflate = new GZIPInputStream(is)) { + reader.parseTsvAndProcess(inflate, TypeTable.Reader.Options.matchAll(), (gav, classes, nestedTypes) -> { + if (gav == null) { + return; + } + + // Process each class in this GAV + for (TypeTable.ClassDefinition classDef : classes.values()) { + // Process each member (method/constructor) in the class + for (TypeTable.Member member : classDef.getMembers()) { + // Check if member has @InlineMe annotation + String annotations = member.getAnnotations(); + if (annotations != null && annotations.contains("InlineMe")) { + InlineMeMethod inlineMethod = extractInlineMeMethod(gav, classDef, member); + if (inlineMethod != null) { + inlineMethods.add(inlineMethod); + } + } + } + } + }); + + // Generate YAML recipes + generateYamlRecipes(inlineMethods, outputPath, recipeName); + + System.out.println("Generated " + inlineMethods.size() + " inline recipes to " + outputPath); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static @Nullable InlineMeMethod extractInlineMeMethod( + TypeTable.GroupArtifactVersion gav, + TypeTable.ClassDefinition classDef, + TypeTable.Member member) { + try { + // Parse the annotations to find @InlineMe + List annotations = + AnnotationDeserializer.parseAnnotations(requireNonNull(member.getAnnotations())); + for (AnnotationDeserializer.AnnotationInfo annotation : annotations) { + if (!annotation.getDescriptor().endsWith("/InlineMe;")) { + continue; + } + + List attributes = annotation.getAttributes(); + if (attributes == null) { + continue; + } + + // Extract annotation values + String replacement = null; + List imports = new ArrayList<>(); + List staticImports = new ArrayList<>(); + + for (AnnotationDeserializer.AttributeInfo attr : attributes) { + switch (attr.getName()) { + case "replacement": + replacement = (String) attr.getValue(); + break; + case "imports": + if (attr.getValue() instanceof Object[]) { + for (Object imp : (Object[]) attr.getValue()) { + imports.add((String) imp); + } + } + break; + case "staticImports": + if (attr.getValue() instanceof Object[]) { + for (Object imp : (Object[]) attr.getValue()) { + staticImports.add((String) imp); + } + } + break; + } + } + + if (replacement != null) { + // Build the method pattern + String methodPattern = buildMethodPattern(classDef, member); + + return new InlineMeMethod( + gav, + methodPattern, + replacement, + imports, + staticImports, + gav.getArtifactId() + "-" + gav.getVersion() + ); + } + } + } catch (Exception e) { + System.err.println("Failed to parse annotations for " + classDef.getName() + "." + member.getName() + ": " + e.getMessage()); + } + + return null; + } + + private static String buildMethodPattern(TypeTable.ClassDefinition classDef, TypeTable.Member member) { + String className = classDef.getName().replace('/', '.'); + String methodName = member.getName(); + + // For constructors, use the class name + if ("".equals(methodName)) { + methodName = className.substring(className.lastIndexOf('.') + 1); + } + + // Parse method descriptor to extract parameter types + String descriptor = member.getDescriptor(); + String paramPattern = parseMethodParameters(descriptor); + + return className + " " + methodName + paramPattern; + } + + private static String parseMethodParameters(String descriptor) { + if (!descriptor.startsWith("(")) { + return "()"; + } + + List paramTypes = new ArrayList<>(); + int i = 1; // Skip opening '(' + while (i < descriptor.length() && descriptor.charAt(i) != ')') { + String type = parseType(descriptor, i); + paramTypes.add(type); + i += getTypeLength(descriptor, i); + } + + if (paramTypes.isEmpty()) { + return "()"; + } + return "(" + String.join(", ", paramTypes) + ")"; + } + + private static String parseType(String descriptor, int start) { + char c = descriptor.charAt(start); + return switch (c) { + case 'B' -> "byte"; + case 'C' -> "char"; + case 'D' -> "double"; + case 'F' -> "float"; + case 'I' -> "int"; + case 'J' -> "long"; + case 'S' -> "short"; + case 'Z' -> "boolean"; + case 'V' -> "void"; + case 'L' -> { + // Object type - extract class name + int semicolon = descriptor.indexOf(';', start); + String className = descriptor.substring(start + 1, semicolon); + yield className.replace('/', '.'); + } + case '[' -> { + // Array type + String elementType = parseType(descriptor, start + 1); + yield elementType + "[]"; + } + default -> "Object"; // Fallback + }; + } + + private static int getTypeLength(String descriptor, int start) { + char c = descriptor.charAt(start); + return switch (c) { + case 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 'V' -> 1; + // Object type - find the semicolon + case 'L' -> descriptor.indexOf(';', start) - start + 1; + // Array type - recurse for element type + case '[' -> 1 + getTypeLength(descriptor, start + 1); + default -> 1; + }; + } + + private static void generateYamlRecipes(List methods, Path outputPath, String recipeName) throws IOException { + StringBuilder yaml = new StringBuilder(); + Path licenseHeader = Paths.get("gradle/licenseHeader.txt"); + if (Files.isRegularFile(licenseHeader)) { + try (Stream lines = Files.lines(licenseHeader)) { + lines.forEach(line -> yaml + .append("# ") + .append(line.replace("${year}", String.valueOf(Year.now().getValue()))) + .append("\n")); + } + } + + yaml.append("#\n"); + yaml.append("# Generated InlineMe recipes from TypeTable\n"); + yaml.append("#\n\n"); + + yaml.append("type: specs.openrewrite.org/v1beta/recipe\n"); + yaml.append(format("name: %s\n", recipeName)); + yaml.append("displayName: Inline methods annotated with `@InlineMe`\n"); + yaml.append("description: >-\n"); + yaml.append(" Automatically generated recipes to inline method calls based on `@InlineMe` annotations\n"); + yaml.append(" discovered in the type table.\n"); + yaml.append("recipeList:\n"); + + // Group methods by GAV for better organization + Map> methodsByGav = + methods.stream().collect(groupingBy(m -> m.gav)); + + for (Map.Entry> entry : methodsByGav.entrySet()) { + TypeTable.GroupArtifactVersion gav = entry.getKey(); + List gavMethods = entry.getValue(); + + yaml.append("\n # From ").append(gav.getGroupId()).append(":").append(gav.getArtifactId()) + .append(":").append(gav.getVersion()).append("\n"); + + for (InlineMeMethod method : gavMethods) { + yaml.append(" - org.openrewrite.java.InlineMethodCalls:\n"); + yaml.append(" methodPattern: '").append(escapeYaml(method.methodPattern)).append("'\n"); + yaml.append(" replacement: '").append(escapeYaml(method.replacement)).append("'\n"); + + if (!method.imports.isEmpty()) { + yaml.append(" imports:\n"); + for (String imp : method.imports) { + yaml.append(" - '").append(escapeYaml(imp)).append("'\n"); + } + } + + if (!method.staticImports.isEmpty()) { + yaml.append(" staticImports:\n"); + for (String imp : method.staticImports) { + yaml.append(" - '").append(escapeYaml(imp)).append("'\n"); + } + } + + yaml.append(" classpathFromResources:\n"); + yaml.append(" - '").append(escapeYaml(method.classpathResource)).append("'\n"); + } + } + + Files.write(outputPath, yaml.toString().getBytes()); + } + + private static String escapeYaml(String value) { + // Escape single quotes by doubling them + return value.replace("'", "''"); + } + + private record InlineMeMethod( + TypeTable.GroupArtifactVersion gav, + String methodPattern, + String replacement, + List imports, + List staticImports, + String classpathResource) { + } +} From 2cc0f47a29778acf1a8320ae09cd3fa78f47e384 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 11:40:20 +0100 Subject: [PATCH 2/9] Minimize generator now that we generate for a single artifactId --- build.gradle.kts | 5 +- .../META-INF/rewrite/inline-guava-methods.yml | 18 +--- .../InlineMethodCallsRecipeGenerator.java | 98 +++++++------------ 3 files changed, 43 insertions(+), 78 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 792abef..14b632b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -111,8 +111,9 @@ tasks { classpath = sourceSets.getByName("test").runtimeClasspath args( "src/main/resources/META-INF/rewrite/classpath.tsv.gz", - "src/main/resources/META-INF/rewrite/inline-guava-methods.yml", - "com.google.guava.InlineGuavaMethods" + "guava", + "src/main/resources/META-INF/rewrite/inline-guava-methods.yml" ) + finalizedBy("licenseFormat") } } diff --git a/src/main/resources/META-INF/rewrite/inline-guava-methods.yml b/src/main/resources/META-INF/rewrite/inline-guava-methods.yml index 3a0cb12..6e3c61e 100644 --- a/src/main/resources/META-INF/rewrite/inline-guava-methods.yml +++ b/src/main/resources/META-INF/rewrite/inline-guava-methods.yml @@ -1,28 +1,14 @@ -# Copyright 2025 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. # -# Generated InlineMe recipes from TypeTable +# Recipes generated for `@InlineMe` annotated methods in com.google.guava:guava:33.5.0-jre # type: specs.openrewrite.org/v1beta/recipe name: com.google.guava.InlineGuavaMethods -displayName: Inline `guava` methods annotated with `@InlineMe` +displayName: Inline Guava methods annotated with `@InlineMe` description: >- Automatically generated recipes to inline method calls based on `@InlineMe` annotations discovered in the type table. recipeList: - # From com.google.guava:guava:33.5.0-jre - org.openrewrite.java.InlineMethodCalls: methodPattern: 'com.google.common.primitives.Booleans hashCode(boolean)' replacement: 'Boolean.hashCode(value)' diff --git a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java index d7585e4..faae698 100644 --- a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java +++ b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java @@ -17,48 +17,43 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.internal.StringUtils; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.Year; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.stream.Stream; import java.util.zip.GZIPInputStream; -import static java.lang.String.format; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.groupingBy; public class InlineMethodCallsRecipeGenerator { public static void main(String[] args) { if (args.length < 3) { - System.err.println("Usage: InlineMethodCallsRecipeGenerator "); + System.err.println("Usage: InlineMethodCallsRecipeGenerator "); System.exit(1); } Path inputPath = Paths.get(args[0]); - Path outputPath = Paths.get(args[1]); - String recipeName = args[2]; + String artifactId = args[1]; + Path outputPath = Paths.get(args[2]); - generate(inputPath, outputPath, recipeName); + generate(inputPath, artifactId, outputPath); } - static void generate(Path tsvFile, Path outputPath, String recipeName) { + static void generate(Path tsvFile, String artifactId, Path outputPath) { List inlineMethods = new ArrayList<>(); TypeTable.Reader reader = new TypeTable.Reader(new InMemoryExecutionContext()); try (InputStream is = Files.newInputStream(tsvFile); InputStream inflate = new GZIPInputStream(is)) { - reader.parseTsvAndProcess(inflate, TypeTable.Reader.Options.matchAll(), (gav, classes, nestedTypes) -> { - if (gav == null) { - return; - } - + TypeTable.Reader.Options options = TypeTable.Reader.Options.builder() + .artifactMatcher(artifactIdVersion -> artifactIdVersion.startsWith(artifactId + '-')) + .build(); + reader.parseTsvAndProcess(inflate, options, (gav, classes, nestedTypes) -> { // Process each class in this GAV for (TypeTable.ClassDefinition classDef : classes.values()) { // Process each member (method/constructor) in the class @@ -76,8 +71,7 @@ static void generate(Path tsvFile, Path outputPath, String recipeName) { }); // Generate YAML recipes - generateYamlRecipes(inlineMethods, outputPath, recipeName); - + generateYamlRecipes(inlineMethods, outputPath); System.out.println("Generated " + inlineMethods.size() + " inline recipes to " + outputPath); } catch (IOException e) { @@ -140,7 +134,7 @@ static void generate(Path tsvFile, Path outputPath, String recipeName) { replacement, imports, staticImports, - gav.getArtifactId() + "-" + gav.getVersion() + gav.getArtifactId() + "-" + gav.getVersion().substring(0, gav.getVersion().indexOf('.')) ); } } @@ -225,63 +219,47 @@ private static int getTypeLength(String descriptor, int start) { }; } - private static void generateYamlRecipes(List methods, Path outputPath, String recipeName) throws IOException { - StringBuilder yaml = new StringBuilder(); - Path licenseHeader = Paths.get("gradle/licenseHeader.txt"); - if (Files.isRegularFile(licenseHeader)) { - try (Stream lines = Files.lines(licenseHeader)) { - lines.forEach(line -> yaml - .append("# ") - .append(line.replace("${year}", String.valueOf(Year.now().getValue()))) - .append("\n")); - } - } + private static void generateYamlRecipes(List methods, Path outputPath) throws IOException { + TypeTable.GroupArtifactVersion gav = methods.getFirst().gav(); + String moduleName = StringUtils.capitalize(gav.getArtifactId()); + StringBuilder yaml = new StringBuilder(); yaml.append("#\n"); - yaml.append("# Generated InlineMe recipes from TypeTable\n"); + yaml.append("# Recipes generated for `@InlineMe` annotated methods in ") + .append(gav.getGroupId()).append(":") + .append(gav.getArtifactId()).append(":") + .append(gav.getVersion()).append("\n"); yaml.append("#\n\n"); yaml.append("type: specs.openrewrite.org/v1beta/recipe\n"); - yaml.append(format("name: %s\n", recipeName)); - yaml.append("displayName: Inline methods annotated with `@InlineMe`\n"); + yaml.append("name: ").append(gav.getGroupId()).append(".Inline").append(moduleName).append("Methods").append("\n"); + yaml.append("displayName: Inline ").append(moduleName).append(" methods annotated with `@InlineMe`\n"); yaml.append("description: >-\n"); yaml.append(" Automatically generated recipes to inline method calls based on `@InlineMe` annotations\n"); yaml.append(" discovered in the type table.\n"); yaml.append("recipeList:\n"); - // Group methods by GAV for better organization - Map> methodsByGav = - methods.stream().collect(groupingBy(m -> m.gav)); - - for (Map.Entry> entry : methodsByGav.entrySet()) { - TypeTable.GroupArtifactVersion gav = entry.getKey(); - List gavMethods = entry.getValue(); + for (InlineMeMethod method : methods) { + yaml.append(" - org.openrewrite.java.InlineMethodCalls:\n"); + yaml.append(" methodPattern: '").append(escapeYaml(method.methodPattern)).append("'\n"); + yaml.append(" replacement: '").append(escapeYaml(method.replacement)).append("'\n"); - yaml.append("\n # From ").append(gav.getGroupId()).append(":").append(gav.getArtifactId()) - .append(":").append(gav.getVersion()).append("\n"); - - for (InlineMeMethod method : gavMethods) { - yaml.append(" - org.openrewrite.java.InlineMethodCalls:\n"); - yaml.append(" methodPattern: '").append(escapeYaml(method.methodPattern)).append("'\n"); - yaml.append(" replacement: '").append(escapeYaml(method.replacement)).append("'\n"); - - if (!method.imports.isEmpty()) { - yaml.append(" imports:\n"); - for (String imp : method.imports) { - yaml.append(" - '").append(escapeYaml(imp)).append("'\n"); - } + if (!method.imports.isEmpty()) { + yaml.append(" imports:\n"); + for (String imp : method.imports) { + yaml.append(" - '").append(escapeYaml(imp)).append("'\n"); } + } - if (!method.staticImports.isEmpty()) { - yaml.append(" staticImports:\n"); - for (String imp : method.staticImports) { - yaml.append(" - '").append(escapeYaml(imp)).append("'\n"); - } + if (!method.staticImports.isEmpty()) { + yaml.append(" staticImports:\n"); + for (String imp : method.staticImports) { + yaml.append(" - '").append(escapeYaml(imp)).append("'\n"); } - - yaml.append(" classpathFromResources:\n"); - yaml.append(" - '").append(escapeYaml(method.classpathResource)).append("'\n"); } + + yaml.append(" classpathFromResources:\n"); + yaml.append(" - '").append(escapeYaml(method.classpathResource)).append("'\n"); } Files.write(outputPath, yaml.toString().getBytes()); From c3ef15333d7daa6d2689d52775209884101d1936 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 11:43:26 +0100 Subject: [PATCH 3/9] Also generate recipes for Log4j --- build.gradle.kts | 12 ++++++++++ .../META-INF/rewrite/inline-guava-methods.yml | 16 +++++++++++++ ...i-methods.yml => inline-log4j-methods.yml} | 24 ++++++++++--------- 3 files changed, 41 insertions(+), 11 deletions(-) rename src/main/resources/META-INF/rewrite/{inline-log4j-api-methods.yml => inline-log4j-methods.yml} (91%) diff --git a/build.gradle.kts b/build.gradle.kts index 14b632b..8e67fe8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -116,4 +116,16 @@ tasks { ) finalizedBy("licenseFormat") } + val generateInlineLog4jMethods by registering(JavaExec::class) { + group = "generate" + description = "Generate Quarkus migration aggregation Recipes." + mainClass = "org.openrewrite.java.internal.parser.InlineMethodCallsRecipeGenerator" + classpath = sourceSets.getByName("test").runtimeClasspath + args( + "src/main/resources/META-INF/rewrite/classpath.tsv.gz", + "log4j", + "src/main/resources/META-INF/rewrite/inline-log4j-methods.yml" + ) + finalizedBy("licenseFormat") + } } diff --git a/src/main/resources/META-INF/rewrite/inline-guava-methods.yml b/src/main/resources/META-INF/rewrite/inline-guava-methods.yml index 6e3c61e..7f55dc1 100644 --- a/src/main/resources/META-INF/rewrite/inline-guava-methods.yml +++ b/src/main/resources/META-INF/rewrite/inline-guava-methods.yml @@ -1,3 +1,19 @@ +# +# Copyright 2025 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. +# + # # Recipes generated for `@InlineMe` annotated methods in com.google.guava:guava:33.5.0-jre # diff --git a/src/main/resources/META-INF/rewrite/inline-log4j-api-methods.yml b/src/main/resources/META-INF/rewrite/inline-log4j-methods.yml similarity index 91% rename from src/main/resources/META-INF/rewrite/inline-log4j-api-methods.yml rename to src/main/resources/META-INF/rewrite/inline-log4j-methods.yml index 3f5bd3d..0596c2a 100644 --- a/src/main/resources/META-INF/rewrite/inline-log4j-api-methods.yml +++ b/src/main/resources/META-INF/rewrite/inline-log4j-methods.yml @@ -1,3 +1,4 @@ +# # Copyright 2025 the original author or authors. #

# Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,24 +13,18 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Generated InlineMe recipes from TypeTable + +# +# Recipes generated for `@InlineMe` annotated methods in org.apache.logging.log4j:log4j-api:2.25.2 # type: specs.openrewrite.org/v1beta/recipe -name: org.apache.logging.log4j.InlineLog4jApiMethods -displayName: Inline `log4j-api-2` methods annotated with `@InlineMe` +name: org.apache.logging.log4j.InlineLog4j-apiMethods +displayName: Inline Log4j-api methods annotated with `@InlineMe` description: >- Automatically generated recipes to inline method calls based on `@InlineMe` annotations discovered in the type table. recipeList: - # From org.apache.logging.log4j:log4j-api:2.25.2 - - org.openrewrite.java.InlineMethodCalls: - methodPattern: 'org.apache.logging.log4j.message.ParameterizedMessage ParameterizedMessage(java.lang.String, java.lang.String[], java.lang.Throwable)' - replacement: 'this(pattern, Arrays.stream(args).toArray(Object[]::new), throwable)' - imports: - - 'java.util.Arrays' - classpathFromResources: - - 'log4j-api-2' - org.openrewrite.java.InlineMethodCalls: methodPattern: 'org.apache.logging.log4j.message.StructuredDataId StructuredDataId(java.lang.String, int, java.lang.String[], java.lang.String[])' replacement: 'this(name, String.valueOf(enterpriseNumber), required, optional)' @@ -45,6 +40,13 @@ recipeList: replacement: 'this.makeId(defaultId, String.valueOf(anEnterpriseNumber))' classpathFromResources: - 'log4j-api-2' + - org.openrewrite.java.InlineMethodCalls: + methodPattern: 'org.apache.logging.log4j.message.ParameterizedMessage ParameterizedMessage(java.lang.String, java.lang.String[], java.lang.Throwable)' + replacement: 'this(pattern, Arrays.stream(args).toArray(Object[]::new), throwable)' + imports: + - 'java.util.Arrays' + classpathFromResources: + - 'log4j-api-2' - org.openrewrite.java.InlineMethodCalls: methodPattern: 'org.apache.logging.log4j.MarkerManager getMarker(java.lang.String, org.apache.logging.log4j.Marker)' replacement: 'MarkerManager.getMarker(name).addParents(parent)' From 48ad896900fed4b633254526982f8fd6871447b4 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 11:50:55 +0100 Subject: [PATCH 4/9] Handle capitalization for log4j-api --- .../resources/META-INF/rewrite/inline-guava-methods.yml | 2 +- .../resources/META-INF/rewrite/inline-log4j-methods.yml | 4 ++-- .../internal/parser/InlineMethodCallsRecipeGenerator.java | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/resources/META-INF/rewrite/inline-guava-methods.yml b/src/main/resources/META-INF/rewrite/inline-guava-methods.yml index 7f55dc1..bc8f3c7 100644 --- a/src/main/resources/META-INF/rewrite/inline-guava-methods.yml +++ b/src/main/resources/META-INF/rewrite/inline-guava-methods.yml @@ -20,7 +20,7 @@ type: specs.openrewrite.org/v1beta/recipe name: com.google.guava.InlineGuavaMethods -displayName: Inline Guava methods annotated with `@InlineMe` +displayName: Inline `guava` methods annotated with `@InlineMe` description: >- Automatically generated recipes to inline method calls based on `@InlineMe` annotations discovered in the type table. diff --git a/src/main/resources/META-INF/rewrite/inline-log4j-methods.yml b/src/main/resources/META-INF/rewrite/inline-log4j-methods.yml index 0596c2a..cd83450 100644 --- a/src/main/resources/META-INF/rewrite/inline-log4j-methods.yml +++ b/src/main/resources/META-INF/rewrite/inline-log4j-methods.yml @@ -19,8 +19,8 @@ # type: specs.openrewrite.org/v1beta/recipe -name: org.apache.logging.log4j.InlineLog4j-apiMethods -displayName: Inline Log4j-api methods annotated with `@InlineMe` +name: org.apache.logging.log4j.InlineLog4jApiMethods +displayName: Inline `log4j-api` methods annotated with `@InlineMe` description: >- Automatically generated recipes to inline method calls based on `@InlineMe` annotations discovered in the type table. diff --git a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java index faae698..16e3c01 100644 --- a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java +++ b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java @@ -25,10 +25,12 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.zip.GZIPInputStream; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; public class InlineMethodCallsRecipeGenerator { @@ -221,7 +223,9 @@ private static int getTypeLength(String descriptor, int start) { private static void generateYamlRecipes(List methods, Path outputPath) throws IOException { TypeTable.GroupArtifactVersion gav = methods.getFirst().gav(); - String moduleName = StringUtils.capitalize(gav.getArtifactId()); + String moduleName = Arrays.stream(gav.getArtifactId().split("-")) + .map(StringUtils::capitalize) + .collect(joining()); StringBuilder yaml = new StringBuilder(); yaml.append("#\n"); @@ -233,7 +237,7 @@ private static void generateYamlRecipes(List methods, Path outpu yaml.append("type: specs.openrewrite.org/v1beta/recipe\n"); yaml.append("name: ").append(gav.getGroupId()).append(".Inline").append(moduleName).append("Methods").append("\n"); - yaml.append("displayName: Inline ").append(moduleName).append(" methods annotated with `@InlineMe`\n"); + yaml.append("displayName: Inline `").append(gav.getArtifactId()).append("` methods annotated with `@InlineMe`\n"); yaml.append("description: >-\n"); yaml.append(" Automatically generated recipes to inline method calls based on `@InlineMe` annotations\n"); yaml.append(" discovered in the type table.\n"); From 9490f08295216bdb4a8847af2e320fcfa843ca44 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 12:02:26 +0100 Subject: [PATCH 5/9] Minimize arguments --- build.gradle.kts | 12 ++----- ...ethods.yml => inline-guava-33-methods.yml} | 2 +- ...ods.yml => inline-log4j-api-2-methods.yml} | 2 +- .../InlineMethodCallsRecipeGenerator.java | 36 ++++++++----------- 4 files changed, 19 insertions(+), 33 deletions(-) rename src/main/resources/META-INF/rewrite/{inline-guava-methods.yml => inline-guava-33-methods.yml} (99%) rename src/main/resources/META-INF/rewrite/{inline-log4j-methods.yml => inline-log4j-api-2-methods.yml} (96%) diff --git a/build.gradle.kts b/build.gradle.kts index 8e67fe8..c53a351 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -109,11 +109,7 @@ tasks { description = "Generate Quarkus migration aggregation Recipes." mainClass = "org.openrewrite.java.internal.parser.InlineMethodCallsRecipeGenerator" classpath = sourceSets.getByName("test").runtimeClasspath - args( - "src/main/resources/META-INF/rewrite/classpath.tsv.gz", - "guava", - "src/main/resources/META-INF/rewrite/inline-guava-methods.yml" - ) + args("guava") finalizedBy("licenseFormat") } val generateInlineLog4jMethods by registering(JavaExec::class) { @@ -121,11 +117,7 @@ tasks { description = "Generate Quarkus migration aggregation Recipes." mainClass = "org.openrewrite.java.internal.parser.InlineMethodCallsRecipeGenerator" classpath = sourceSets.getByName("test").runtimeClasspath - args( - "src/main/resources/META-INF/rewrite/classpath.tsv.gz", - "log4j", - "src/main/resources/META-INF/rewrite/inline-log4j-methods.yml" - ) + args("log4j-api") finalizedBy("licenseFormat") } } diff --git a/src/main/resources/META-INF/rewrite/inline-guava-methods.yml b/src/main/resources/META-INF/rewrite/inline-guava-33-methods.yml similarity index 99% rename from src/main/resources/META-INF/rewrite/inline-guava-methods.yml rename to src/main/resources/META-INF/rewrite/inline-guava-33-methods.yml index bc8f3c7..c83ddf5 100644 --- a/src/main/resources/META-INF/rewrite/inline-guava-methods.yml +++ b/src/main/resources/META-INF/rewrite/inline-guava-33-methods.yml @@ -15,7 +15,7 @@ # # -# Recipes generated for `@InlineMe` annotated methods in com.google.guava:guava:33.5.0-jre +# Recipes generated for `@InlineMe` annotated methods in `com.google.guava:guava:33.5.0-jre` # type: specs.openrewrite.org/v1beta/recipe diff --git a/src/main/resources/META-INF/rewrite/inline-log4j-methods.yml b/src/main/resources/META-INF/rewrite/inline-log4j-api-2-methods.yml similarity index 96% rename from src/main/resources/META-INF/rewrite/inline-log4j-methods.yml rename to src/main/resources/META-INF/rewrite/inline-log4j-api-2-methods.yml index cd83450..d194a99 100644 --- a/src/main/resources/META-INF/rewrite/inline-log4j-methods.yml +++ b/src/main/resources/META-INF/rewrite/inline-log4j-api-2-methods.yml @@ -15,7 +15,7 @@ # # -# Recipes generated for `@InlineMe` annotated methods in org.apache.logging.log4j:log4j-api:2.25.2 +# Recipes generated for `@InlineMe` annotated methods in `org.apache.logging.log4j:log4j-api:2.25.2` # type: specs.openrewrite.org/v1beta/recipe diff --git a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java index 16e3c01..da174a3 100644 --- a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java +++ b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java @@ -35,23 +35,18 @@ public class InlineMethodCallsRecipeGenerator { public static void main(String[] args) { - if (args.length < 3) { - System.err.println("Usage: InlineMethodCallsRecipeGenerator "); + if (args.length < 1) { + System.err.println("Usage: InlineMethodCallsRecipeGenerator "); System.exit(1); } - - Path inputPath = Paths.get(args[0]); - String artifactId = args[1]; - Path outputPath = Paths.get(args[2]); - - generate(inputPath, artifactId, outputPath); + generate(args[0]); } - static void generate(Path tsvFile, String artifactId, Path outputPath) { + static void generate(String artifactId) { List inlineMethods = new ArrayList<>(); TypeTable.Reader reader = new TypeTable.Reader(new InMemoryExecutionContext()); - try (InputStream is = Files.newInputStream(tsvFile); InputStream inflate = new GZIPInputStream(is)) { + try (InputStream is = ClassLoader.getSystemResourceAsStream(TypeTable.DEFAULT_RESOURCE_PATH); InputStream inflate = new GZIPInputStream(is)) { TypeTable.Reader.Options options = TypeTable.Reader.Options.builder() .artifactMatcher(artifactIdVersion -> artifactIdVersion.startsWith(artifactId + '-')) .build(); @@ -72,10 +67,7 @@ static void generate(Path tsvFile, String artifactId, Path outputPath) { } }); - // Generate YAML recipes - generateYamlRecipes(inlineMethods, outputPath); - System.out.println("Generated " + inlineMethods.size() + " inline recipes to " + outputPath); - + generateYamlRecipes(inlineMethods); } catch (IOException e) { throw new RuntimeException(e); } @@ -129,15 +121,14 @@ static void generate(Path tsvFile, String artifactId, Path outputPath) { if (replacement != null) { // Build the method pattern String methodPattern = buildMethodPattern(classDef, member); - + String classpathResource = gav.getArtifactId() + "-" + gav.getVersion().substring(0, gav.getVersion().indexOf('.')); return new InlineMeMethod( gav, methodPattern, replacement, imports, staticImports, - gav.getArtifactId() + "-" + gav.getVersion().substring(0, gav.getVersion().indexOf('.')) - ); + classpathResource); } } } catch (Exception e) { @@ -221,18 +212,20 @@ private static int getTypeLength(String descriptor, int start) { }; } - private static void generateYamlRecipes(List methods, Path outputPath) throws IOException { - TypeTable.GroupArtifactVersion gav = methods.getFirst().gav(); + private static void generateYamlRecipes(List methods) throws IOException { + InlineMeMethod firstMethod = methods.getFirst(); + TypeTable.GroupArtifactVersion gav = firstMethod.gav(); String moduleName = Arrays.stream(gav.getArtifactId().split("-")) .map(StringUtils::capitalize) .collect(joining()); + Path outputPath = Paths.get("src/main/resources/META-INF/rewrite/inline-%s-methods.yml".formatted(firstMethod.classpathResource)); StringBuilder yaml = new StringBuilder(); yaml.append("#\n"); - yaml.append("# Recipes generated for `@InlineMe` annotated methods in ") + yaml.append("# Recipes generated for `@InlineMe` annotated methods in `") .append(gav.getGroupId()).append(":") .append(gav.getArtifactId()).append(":") - .append(gav.getVersion()).append("\n"); + .append(gav.getVersion()).append("`\n"); yaml.append("#\n\n"); yaml.append("type: specs.openrewrite.org/v1beta/recipe\n"); @@ -267,6 +260,7 @@ private static void generateYamlRecipes(List methods, Path outpu } Files.write(outputPath, yaml.toString().getBytes()); + System.out.println("Generated " + methods.size() + " inline recipes to " + outputPath); } private static String escapeYaml(String value) { From 11110c60a0a408782c7d29adf4f85551a60f7c10 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 12:03:48 +0100 Subject: [PATCH 6/9] Use `Path.of` --- .../java/internal/parser/InlineMethodCallsRecipeGenerator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java index da174a3..7263582 100644 --- a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java +++ b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java @@ -23,7 +23,6 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -218,7 +217,7 @@ private static void generateYamlRecipes(List methods) throws IOE String moduleName = Arrays.stream(gav.getArtifactId().split("-")) .map(StringUtils::capitalize) .collect(joining()); - Path outputPath = Paths.get("src/main/resources/META-INF/rewrite/inline-%s-methods.yml".formatted(firstMethod.classpathResource)); + Path outputPath = Path.of("src/main/resources/META-INF/rewrite/inline-%s-methods.yml".formatted(firstMethod.classpathResource)); StringBuilder yaml = new StringBuilder(); yaml.append("#\n"); From e89fdd70d095f83153bae0520130469222eed65e Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 12:06:12 +0100 Subject: [PATCH 7/9] Restore `runtimeOnly` for `rewrite-java` --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index c53a351..e28f7f7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,7 @@ val rewriteVersion = rewriteRecipe.rewriteVersion.get() dependencies { implementation(platform("org.openrewrite:rewrite-bom:$rewriteVersion")) - implementation("org.openrewrite:rewrite-java") + runtimeOnly("org.openrewrite:rewrite-java") runtimeOnly("org.openrewrite:rewrite-templating:${rewriteVersion}") runtimeOnly("ai.timefold.solver:timefold-solver-migration:latest.release") { From f55fe56ed9bd20d5912fe9e035c526501f676630 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 12:09:00 +0100 Subject: [PATCH 8/9] Update instructions --- .../java/internal/parser/InlineMethodCallsRecipeGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java index 7263582..a6c098e 100644 --- a/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java +++ b/src/test/java/org/openrewrite/java/internal/parser/InlineMethodCallsRecipeGenerator.java @@ -35,7 +35,7 @@ public class InlineMethodCallsRecipeGenerator { public static void main(String[] args) { if (args.length < 1) { - System.err.println("Usage: InlineMethodCallsRecipeGenerator "); + System.err.println("Usage: InlineMethodCallsRecipeGenerator "); System.exit(1); } generate(args[0]); From b41ef595d5e1c001640055b2d2f7ab4c801d4daa Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Nov 2025 12:10:05 +0100 Subject: [PATCH 9/9] Update test to handle any resource file path --- src/test/java/com/google/guava/InlineGuavaMethodsTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/com/google/guava/InlineGuavaMethodsTest.java b/src/test/java/com/google/guava/InlineGuavaMethodsTest.java index 65167f7..873c67d 100644 --- a/src/test/java/com/google/guava/InlineGuavaMethodsTest.java +++ b/src/test/java/com/google/guava/InlineGuavaMethodsTest.java @@ -26,9 +26,7 @@ class InlineGuavaMethodsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipeFromResource( - "/META-INF/rewrite/inline-guava-methods.yml", - "com.google.guava.InlineGuavaMethods"); + spec.recipeFromResources("com.google.guava.InlineGuavaMethods"); } @DocumentExample