Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recipe for adding/replacing a Maven runtime config #4363

Merged
merged 6 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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<AddRuntimeConfig.Accumulator> {
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<?, ExecutionContext> getScanner(Accumulator acc) {
return new TreeVisitor<Tree, ExecutionContext>() {
@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<? extends SourceFile> 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<?, ExecutionContext> getVisitor(Accumulator acc) {
return Preconditions.check(acc.isMavenProject() && acc.getMatchingRuntimeConfigFile() != null,
new PlainTextVisitor<ExecutionContext>() {
@Override
public PlainText visitText(PlainText plainText, ExecutionContext executionContext) {
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
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";
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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(
"""
<project>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
</project>
"""
);

@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)
)
);
}
}
Loading