diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddRuntimeConfig.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddRuntimeConfig.java new file mode 100644 index 00000000000..132506987d3 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddRuntimeConfig.java @@ -0,0 +1,172 @@ +/* + * 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.maven; + +import lombok.*; +import org.openrewrite.*; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.text.PlainText; +import org.openrewrite.text.PlainTextVisitor; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Value +@EqualsAndHashCode(callSuper = false) +public class AddRuntimeConfig extends ScanningRecipe { + static final String POM_FILENAME = "pom.xml"; + static final String MVN_CONFIG_DIR = ".mvn"; + static final String MAVEN_CONFIG_FILENAME = "maven.config"; + static final String MAVEN_CONFIG_PATH = MVN_CONFIG_DIR + "/" + MAVEN_CONFIG_FILENAME; + static final String JVM_CONFIG_FILENAME = "jvm.config"; + static final String JVM_CONFIG_PATH = MVN_CONFIG_DIR + "/" + JVM_CONFIG_FILENAME; + + @Option(displayName = "Config file", + description = "The file name for setting the runtime configuration.", + valid = {MAVEN_CONFIG_FILENAME, JVM_CONFIG_FILENAME}, + example = "maven.config") + String relativeConfigFileName; + + @Option(displayName = "Runtime flag", + description = "The runtime flag name to be set.", + example = "-T") + String flag; + + @Option(displayName = "Runtime flag argument", + description = "The argument to set for the runtime flag. Some flags do not need to provide a value.", + required = false, + example = "3") + @Nullable + String argument; + + @Option(displayName = "Separator between runtime flag and argument", + description = "The separator to use if flag and argument have been provided.", + valid = {"", " ", "="}, + example = "=") + Separator separator; + + @Getter + public enum Separator { + NONE(""), + SPACE(" "), + EQUALS("="); + + private final String notation; + + Separator(String notation) { + this.notation = notation; + } + } + + @Override + public String getDisplayName() { + return "Add a configuration option for the Maven runtime"; + } + + @Override + public String getDescription() { + return "Add a new configuration option for the Maven runtime if not already present."; + } + + @Data + @RequiredArgsConstructor + public static class Accumulator { + final String targetRepresentation; + boolean mavenProject; + Path matchingRuntimeConfigFile; + + } + + @Override + public Accumulator getInitialValue(ExecutionContext ctx) { + String targetRepresentation = argument == null ? flag : flag + separator.getNotation() + argument; + return new Accumulator(targetRepresentation); + } + + @Override + public TreeVisitor getScanner(Accumulator acc) { + return new TreeVisitor() { + @Override + public Tree preVisit(Tree tree, ExecutionContext ctx) { + stopAfterPreVisit(); + if (tree instanceof SourceFile) { + Path sourcePath = ((SourceFile) tree).getSourcePath(); + switch (PathUtils.separatorsToUnix(sourcePath.toString())) { + case POM_FILENAME: + acc.setMavenProject(true); + break; + case MAVEN_CONFIG_PATH: + case JVM_CONFIG_PATH: + acc.setMatchingRuntimeConfigFile(sourcePath); + break; + default: + break; + } + } + return tree; + } + }; + } + + @Override + public Collection generate(Accumulator acc, ExecutionContext ctx) { + if (acc.isMavenProject() && acc.getMatchingRuntimeConfigFile() == null) { + return Collections.singletonList(PlainText.builder() + .text(acc.getTargetRepresentation()) + .sourcePath(Paths.get(MVN_CONFIG_DIR, relativeConfigFileName)) + .build()); + } + return Collections.emptyList(); + } + + @Override + public TreeVisitor getVisitor(Accumulator acc) { + return Preconditions.check(acc.isMavenProject() && acc.getMatchingRuntimeConfigFile() != null, + new PlainTextVisitor() { + @Override + public PlainText visitText(PlainText plainText, ExecutionContext ctx) { + if (plainText.getSourcePath().equals(acc.getMatchingRuntimeConfigFile())) { + return addOrReplaceConfig(plainText, acc); + } + return plainText; + } + + private PlainText addOrReplaceConfig(PlainText plainText, Accumulator acc) { + String existingContent = plainText.getText(); + Matcher matcher = Pattern.compile(Pattern.quote(flag) + "[=\\s]?[a-zA-Z0-9]*").matcher(existingContent); + if (matcher.find()) { + return plainText.withText(matcher.replaceAll(acc.getTargetRepresentation())); + } + + String newText = StringUtils.isBlank(existingContent) ? existingContent : existingContent + determineConfigSeparator(plainText); + return plainText.withText(newText + acc.getTargetRepresentation()); + } + + private String determineConfigSeparator(PlainText plainText) { + // Use new line for maven.config, space for jvm.config + if (Paths.get(JVM_CONFIG_PATH).equals(plainText.getSourcePath())) { + return " "; + } + return plainText.getText().contains("\r\n") ? "\r\n" : "\n"; + } + }); + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddRuntimeConfigTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddRuntimeConfigTest.java new file mode 100644 index 00000000000..48774a1a182 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddRuntimeConfigTest.java @@ -0,0 +1,175 @@ +/* + * 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.maven; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpecs; + +import static org.openrewrite.maven.AddRuntimeConfig.*; +import static org.openrewrite.maven.Assertions.pomXml; +import static org.openrewrite.test.SourceSpecs.text; + +class AddRuntimeConfigTest implements RewriteTest { + private static final SourceSpecs POM_XML_SOURCE_SPEC = pomXml( + """ + + com.mycompany.app + my-app + 1 + + """ + ); + + @Test + @DocumentExample + void createConfigFileWithRuntimeConfigIfFileDoesNotExist() { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(MAVEN_CONFIG_FILENAME, "-T", "3", Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + null, + "-T=3", + spec -> spec.path(MAVEN_CONFIG_PATH) + ) + ); + } + + @Test + void appendRuntimeFlagToEmptyConfigFile() { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(MAVEN_CONFIG_FILENAME, "-T", "3", Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + "", + "-T=3", + spec -> spec.path(MAVEN_CONFIG_PATH) + ) + ); + } + + @ParameterizedTest + @EnumSource(Separator.class) + void createConfigFileWithRuntimeConfigForAllSeparators(Separator separator) { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(MAVEN_CONFIG_FILENAME, "-T", "3", separator)), + POM_XML_SOURCE_SPEC, + text( + "", + "-T" + separator.getNotation() + "3", + spec -> spec.path(MAVEN_CONFIG_PATH) + ) + ); + } + + @Test + void appendRuntimeFlagIfItDoesNotExist() { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(MAVEN_CONFIG_FILENAME, "-T", "3", Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + "-U", + """ + -U + -T=3 + """, + spec -> spec.path(MAVEN_CONFIG_PATH) + ) + ); + } + + @Test + void doesNotModifyRuntimeFlagIfExistingWithoutArgument() { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(MAVEN_CONFIG_FILENAME, "-U", null, Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + "-U", + spec -> spec.path(MAVEN_CONFIG_PATH) + ) + ); + } + + @Test + void doesNotModifyRuntimeFlagIfExistingWithSameArgument() { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(MAVEN_CONFIG_FILENAME, "-T", "3", Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + "-T=3", + spec -> spec.path(MAVEN_CONFIG_PATH) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"--threads=2", "--threads=3"}) + void appendRuntimeFlagIfExistingForFlagFormatMismatch(String existingConfig) { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(MAVEN_CONFIG_FILENAME, "-T", "3", Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + existingConfig, + existingConfig + System.lineSeparator() + "-T=3", + spec -> spec.path(MAVEN_CONFIG_PATH) + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = {"-T 3", "-T3", "-T=3"}) + void replaceRuntimeFlagIfExistingWithDifferentArgument(String existingConfig) { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(MAVEN_CONFIG_FILENAME, "-T", "4", Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + existingConfig, + "-T=4", + spec -> spec.path(MAVEN_CONFIG_PATH) + ) + ); + } + + @Test + void addJvmRuntimeFlagOnTheSameLine() { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(JVM_CONFIG_FILENAME, "-XX:MaxPermSize", "512m", Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + "-Xmx2048m -Xms1024m", + "-Xmx2048m -Xms1024m -XX:MaxPermSize=512m", + spec -> spec.path(JVM_CONFIG_PATH) + ) + ); + } + + @Test + void replaceJvmRuntimeFlagOnTheSameLine() { + rewriteRun( + spec -> spec.recipe(new AddRuntimeConfig(JVM_CONFIG_FILENAME, "-XX:MaxPermSize", "1024m", Separator.EQUALS)), + POM_XML_SOURCE_SPEC, + text( + "-Xmx2048m -XX:MaxPermSize=512m -Xms1024m", + "-Xmx2048m -XX:MaxPermSize=1024m -Xms1024m", + spec -> spec.path(JVM_CONFIG_PATH) + ) + ); + } +}