Skip to content

Instantly share code, notes, and snippets.

@gnodet
Created January 8, 2025 20:44
Show Gist options
  • Save gnodet/c297ccae07dd2ee67dfa98cf1ef128f9 to your computer and use it in GitHub Desktop.
Save gnodet/c297ccae07dd2ee67dfa98cf1ef128f9 to your computer and use it in GitHub Desktop.
JBang script to startup migrating a plugin to the Maven 4 API
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.github.javaparser:javaparser-core:3.25.7
//DEPS org.apache.commons:commons-lang3:3.14.0
//DEPS info.picocli:picocli:4.7.5
//DEPS org.dom4j:dom4j:2.1.4
//JAVA 17
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.stream.Stream;
@Command(name = "maven4-migrate", mixinStandardHelpOptions = true, version = "maven4-migrate 1.0",
description = "Migrates Maven plugins and POM files to Maven 4 API")
public class Maven4Migration implements Callable<Integer> {
private static final Map<String, String> IMPORT_REPLACEMENTS = new HashMap<>();
private static final Map<String, String> PHASE_REPLACEMENTS = new HashMap<>();
private static final Map<String, String> EXCEPTION_REPLACEMENTS = new HashMap<>();
private static final Map<String, String> TYPE_REPLACEMENTS = new HashMap<>();
static {
IMPORT_REPLACEMENTS.put("org.apache.maven.plugins.annotations.Mojo",
"org.apache.maven.api.plugin.annotations.Mojo");
IMPORT_REPLACEMENTS.put("org.apache.maven.plugins.annotations.Parameter",
"org.apache.maven.api.plugin.annotations.Parameter");
IMPORT_REPLACEMENTS.put("org.apache.maven.plugins.annotations.Component",
"org.apache.maven.api.di.Inject");
IMPORT_REPLACEMENTS.put("org.apache.maven.project.MavenProject",
"org.apache.maven.api.Project");
IMPORT_REPLACEMENTS.put("org.apache.maven.execution.MavenSession",
"org.apache.maven.api.Session");
IMPORT_REPLACEMENTS.put("org.apache.maven.plugin.MojoExecutionException",
"org.apache.maven.api.plugin.MojoException");
IMPORT_REPLACEMENTS.put("org.apache.maven.plugin.MojoFailureException",
"org.apache.maven.api.plugin.MojoException");
// Initialize type replacements
TYPE_REPLACEMENTS.put("MavenProject", "Project");
TYPE_REPLACEMENTS.put("MavenSession", "Session");
// Initialize phase replacements
PHASE_REPLACEMENTS.put("GENERATE_SOURCES", "BEFORE + SOURCES");
PHASE_REPLACEMENTS.put("PROCESS_SOURCES", "AFTER + SOURCES");
PHASE_REPLACEMENTS.put("GENERATE_RESOURCES", "BEFORE + RESOURCES");
PHASE_REPLACEMENTS.put("PROCESS_RESOURCES", "AFTER + RESOURCES");
PHASE_REPLACEMENTS.put("PROCESS_CLASSES", "AFTER + COMPILE");
PHASE_REPLACEMENTS.put("GENERATE_TEST_SOURCES", "BEFORE + TEST_SOURCES");
PHASE_REPLACEMENTS.put("PROCESS_TEST_SOURCES", "AFTER + TEST_SOURCES");
PHASE_REPLACEMENTS.put("GENERATE_TEST_RESOURCES", "BEFORE + TEST_RESOURCES");
PHASE_REPLACEMENTS.put("PROCESS_TEST_RESOURCES", "AFTER + TEST_RESOURCES");
PHASE_REPLACEMENTS.put("PROCESS_TEST_CLASSES", "AFTER + TEST_COMPILE");
PHASE_REPLACEMENTS.put("PREPARE_PACKAGE", "BEFORE + PACKAGE");
PHASE_REPLACEMENTS.put("PRE_INTEGRATION_TEST", "BEFORE + INTEGRATION_TEST");
PHASE_REPLACEMENTS.put("POST_INTEGRATION_TEST", "AFTER + INTEGRATION_TEST");
// Initialize exception replacements
EXCEPTION_REPLACEMENTS.put("MojoExecutionException", "MojoException");
EXCEPTION_REPLACEMENTS.put("MojoFailureException", "MojoException");
}
@Parameters(index = "0", description = "The source directory containing files to migrate")
private Path sourceDir;
@Option(names = {"-r", "--recursive"}, description = "Search directories recursively")
private boolean recursive = false;
@Option(names = {"-p", "--pom"}, description = "Process pom.xml files")
private boolean processPom = false;
@Override
public Integer call() throws Exception {
// First process pom.xml in root directory if requested
if (processPom) {
Path pomFile = sourceDir.resolve("pom.xml");
if (Files.exists(pomFile)) {
processPomFile(pomFile);
}
}
// Then process Java files
try (Stream<Path> paths = recursive ?
Files.walk(sourceDir) :
Files.list(sourceDir)) {
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(this::processJavaFile);
}
return 0;
}
private void processPomFile(Path file) {
try {
System.out.println("Processing POM: " + file);
SAXReader reader = new SAXReader();
Document document = reader.read(file.toFile());
Element root = document.getRootElement();
boolean modified = false;
modified |= updateJavaVersion(root);
modified |= updateMavenVersion(root);
modified |= addMavenDependencies(root);
if (modified) {
// Configure pretty print OutputFormat
org.dom4j.io.OutputFormat format = org.dom4j.io.OutputFormat.createPrettyPrint();
format.setIndentSize(2);
format.setNewlines(true);
XMLWriter writer = new XMLWriter(new FileOutputStream(file.toFile()), format);
writer.write(document);
writer.close();
System.out.println("Modified POM: " + file);
}
} catch (Exception e) {
System.err.println("Error processing POM file: " + file);
e.printStackTrace();
}
}
private boolean updateJavaVersion(Element root) {
Element properties = root.element("properties");
if (properties != null) {
Element javaVersion = properties.element("javaVersion");
if (javaVersion != null) {
String currentVersion = javaVersion.getTextTrim();
try {
if (Double.parseDouble(currentVersion) < 17) {
javaVersion.setText("17");
return true;
}
} catch (NumberFormatException e) {
// If version is not a number, replace it anyway
javaVersion.setText("17");
return true;
}
}
}
return false;
}
private boolean updateMavenVersion(Element root) {
Element properties = root.element("properties");
if (properties != null) {
Element mavenVersion = properties.element("mavenVersion");
if (mavenVersion != null) {
String currentVersion = mavenVersion.getTextTrim();
if (!currentVersion.equals("4.0.0-rc-2")) {
mavenVersion.setText("4.0.0-rc-2");
return true;
}
}
}
return false;
}
private boolean addMavenDependencies(Element root) {
Element dependencies = root.element("dependencies");
if (dependencies == null) {
dependencies = root.addElement("dependencies");
}
// Check if dependencies are already present
boolean hasApiCore = dependencies.elements().stream()
.anyMatch(dep -> isMatchingDependency((Element) dep, "org.apache.maven", "maven-api-core"));
if (!hasApiCore) {
// Create new dependencies and add them at the beginning
String[][] mavenDeps = {
{"maven-api-core", "Core API"},
{"maven-api-model", "Model API"},
{"maven-api-di", "Dependency Injection API"},
{"maven-api-plugin", "Plugin API"}
};
// Keep existing elements
java.util.List<Element> existingElements = new java.util.ArrayList<>(dependencies.elements());
// Clear the dependencies element
dependencies.clearContent();
// Add comment
dependencies.addComment(" maven api ");
// Add new Maven dependencies
for (String[] dep : mavenDeps) {
Element dependency = dependencies.addElement("dependency");
dependency.addElement("groupId").setText("org.apache.maven");
dependency.addElement("artifactId").setText(dep[0]);
dependency.addElement("version").setText("${mavenVersion}");
}
// Add back existing dependencies
for (Element element : existingElements) {
dependencies.add(element.createCopy());
}
return true;
}
return false;
}
private boolean isMatchingDependency(Element dep, String groupId, String artifactId) {
Element groupIdElement = dep.element("groupId");
Element artifactIdElement = dep.element("artifactId");
return groupIdElement != null && artifactIdElement != null &&
groupIdElement.getTextTrim().equals(groupId) &&
artifactIdElement.getTextTrim().equals(artifactId);
}
private void processJavaFile(Path file) {
try {
System.out.println("Processing: " + file);
String content = Files.readString(file);
JavaParser parser = new JavaParser();
CompilationUnit cu = parser.parse(content).getResult().orElseThrow();
boolean modified = false;
// First update the annotations and other content while original imports are intact
modified |= updateMojoAnnotations(cu);
modified |= updateDependencyInjection(cu);
modified |= updateMojoImplementation(cu);
modified |= updateThrowsClauses(cu);
modified |= updateExceptionConstructions(cu); // Add new method call
modified |= updateTypeReferences(cu); // Add new method call
modified |= updateModelImports(cu); // Add new method call
modified |= updateLogging(cu); // Add new method call
// Then update the imports last
modified |= updateImports(cu);
if (modified) {
Files.writeString(file, cu.toString());
System.out.println("Modified: " + file);
}
} catch (IOException e) {
System.err.println("Error processing file: " + file);
e.printStackTrace();
}
}
private boolean updateModelImports(CompilationUnit cu) {
boolean modified = false;
NodeList<ImportDeclaration> imports = cu.getImports();
// Handle both static and non-static imports from the model package
for (ImportDeclaration importDecl : imports) {
String name = importDecl.getNameAsString();
if (name.startsWith("org.apache.maven.model.")) {
// Replace the package part while keeping the rest of the import intact
String newName = "org.apache.maven.api.model." +
name.substring("org.apache.maven.model.".length());
importDecl.setName(newName);
modified = true;
}
}
return modified;
}
private boolean updateLogging(CompilationUnit cu) {
boolean modified = false;
// Find classes that implement Mojo
for (ClassOrInterfaceDeclaration classDecl : cu.findAll(ClassOrInterfaceDeclaration.class)) {
if (implementsMojo(classDecl) && !hasParentImplementingMojo(classDecl)) {
// Check if the class or its subclasses use getLog()
if (containsGetLogCall(classDecl) || subclassesContainGetLogCall(classDecl)) {
// Add log field if not present
if (!hasLogField(classDecl)) {
// Add import for Log
cu.addImport("org.apache.maven.api.plugin.Log");
cu.addImport("org.apache.maven.api.di.Inject");
// Create log field
NodeList<Modifier> modifiers = new NodeList<>(Modifier.privateModifier());
FieldDeclaration logField = new FieldDeclaration(
modifiers,
new VariableDeclarator(
new ClassOrInterfaceType(null, "Log"),
"log"
)
);
logField.addAnnotation("Inject");
classDecl.getMembers().add(logField);
// Add getter if not present
if (!hasGetLogMethod(classDecl)) {
// Create getLog method
NodeList<Modifier> methodModifiers = new NodeList<>(Modifier.protectedModifier());
MethodDeclaration getLogMethod = new MethodDeclaration(
methodModifiers,
new ClassOrInterfaceType(null, "Log"),
"getLog"
);
BlockStmt body = new BlockStmt();
body.addStatement(new ReturnStmt(new NameExpr("log")));
getLogMethod.setBody(body);
classDecl.getMembers().add(getLogMethod);
}
modified = true;
}
}
}
}
return modified;
}
private boolean hasParentImplementingMojo(ClassOrInterfaceDeclaration classDecl) {
try {
return classDecl.getExtendedTypes().stream()
.map(type -> {
try {
return type.resolve();
} catch (Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.map(resolvedType -> {
try {
return resolvedType.asReferenceType().getTypeDeclaration();
} catch (Exception e) {
return Optional.<ResolvedReferenceTypeDeclaration>empty();
}
})
.filter(Optional::isPresent)
.map(Optional::get)
.filter(decl -> decl instanceof ClassOrInterfaceDeclaration)
.map(decl -> (ClassOrInterfaceDeclaration) decl)
.anyMatch(this::implementsMojo);
} catch (Exception e) {
// If resolution fails, assume no parent implements Mojo
return false;
}
}
private boolean implementsMojo(ClassOrInterfaceDeclaration classDecl) {
return classDecl.getImplementedTypes().stream()
.anyMatch(type -> type.getNameAsString().endsWith("Mojo"));
}
private boolean containsGetLogCall(ClassOrInterfaceDeclaration classDecl) {
return classDecl.findAll(MethodCallExpr.class).stream()
.anyMatch(call -> call.getNameAsString().equals("getLog"));
}
private boolean subclassesContainGetLogCall(ClassOrInterfaceDeclaration classDecl) {
// Find all subclasses in the same compilation unit
return classDecl.findAll(ClassOrInterfaceDeclaration.class).stream()
.filter(cls -> cls.getExtendedTypes().stream()
.anyMatch(type -> type.getNameAsString().equals(classDecl.getNameAsString())))
.anyMatch(this::containsGetLogCall);
}
private boolean hasLogField(ClassOrInterfaceDeclaration classDecl) {
return classDecl.getFields().stream()
.anyMatch(field -> field.getVariable(0).getNameAsString().equals("log") &&
field.getElementType().asString().equals("Log"));
}
private boolean hasGetLogMethod(ClassOrInterfaceDeclaration classDecl) {
return classDecl.getMethods().stream()
.anyMatch(method -> method.getNameAsString().equals("getLog"));
}
private boolean updateTypeReferences(CompilationUnit cu) {
boolean modified = false;
// Update variable declarations
for (com.github.javaparser.ast.body.VariableDeclarator var : cu.findAll(com.github.javaparser.ast.body.VariableDeclarator.class)) {
if (var.getType() instanceof ClassOrInterfaceType) {
ClassOrInterfaceType type = (ClassOrInterfaceType) var.getType();
String typeName = type.getNameAsString();
if (TYPE_REPLACEMENTS.containsKey(typeName)) {
type.setName(TYPE_REPLACEMENTS.get(typeName));
modified = true;
}
}
}
// Update method parameters
for (com.github.javaparser.ast.body.Parameter param : cu.findAll(com.github.javaparser.ast.body.Parameter.class)) {
if (param.getType() instanceof ClassOrInterfaceType) {
ClassOrInterfaceType type = (ClassOrInterfaceType) param.getType();
String typeName = type.getNameAsString();
if (TYPE_REPLACEMENTS.containsKey(typeName)) {
type.setName(TYPE_REPLACEMENTS.get(typeName));
modified = true;
}
}
}
// Update method return types
for (com.github.javaparser.ast.body.MethodDeclaration method : cu.findAll(com.github.javaparser.ast.body.MethodDeclaration.class)) {
if (method.getType() instanceof ClassOrInterfaceType) {
ClassOrInterfaceType type = (ClassOrInterfaceType) method.getType();
String typeName = type.getNameAsString();
if (TYPE_REPLACEMENTS.containsKey(typeName)) {
type.setName(TYPE_REPLACEMENTS.get(typeName));
modified = true;
}
}
}
// Update field declarations
for (com.github.javaparser.ast.body.FieldDeclaration field : cu.findAll(com.github.javaparser.ast.body.FieldDeclaration.class)) {
if (field.getElementType() instanceof ClassOrInterfaceType) {
ClassOrInterfaceType type = (ClassOrInterfaceType) field.getElementType();
String typeName = type.getNameAsString();
if (TYPE_REPLACEMENTS.containsKey(typeName)) {
type.setName(TYPE_REPLACEMENTS.get(typeName));
modified = true;
}
}
}
// Update type references in generic type arguments
for (ClassOrInterfaceType type : cu.findAll(ClassOrInterfaceType.class)) {
if (type.getTypeArguments().isPresent()) {
for (com.github.javaparser.ast.type.Type typeArg : type.getTypeArguments().get()) {
if (typeArg instanceof ClassOrInterfaceType) {
ClassOrInterfaceType argType = (ClassOrInterfaceType) typeArg;
String typeName = argType.getNameAsString();
if (TYPE_REPLACEMENTS.containsKey(typeName)) {
argType.setName(TYPE_REPLACEMENTS.get(typeName));
modified = true;
}
}
}
}
}
return modified;
}
private boolean updateImports(CompilationUnit cu) {
boolean modified = false;
NodeList<ImportDeclaration> imports = cu.getImports();
for (ImportDeclaration importDecl : imports) {
String name = importDecl.getNameAsString();
if (IMPORT_REPLACEMENTS.containsKey(name)) {
importDecl.setName(IMPORT_REPLACEMENTS.get(name));
modified = true;
}
}
return modified;
}
private boolean updateMojoImplementation(CompilationUnit cu) {
boolean modified = false;
for (ClassOrInterfaceDeclaration classDecl : cu.findAll(ClassOrInterfaceDeclaration.class)) {
// First check if we need to make changes
boolean hasAbstractMojo = classDecl.getExtendedTypes().stream()
.anyMatch(type -> type.getNameAsString().equals("AbstractMojo"));
if (hasAbstractMojo) {
// Clear all extended types
classDecl.getExtendedTypes().clear();
// Add implements org.apache.maven.api.plugin.Mojo
classDecl.addImplementedType("org.apache.maven.api.plugin.Mojo");
modified = true;
}
}
return modified;
}
private boolean updateThrowsClauses(CompilationUnit cu) {
boolean modified = false;
cu.findAll(ClassOrInterfaceDeclaration.class).forEach(classDecl -> {
classDecl.getMethods().forEach(method -> {
NodeList<com.github.javaparser.ast.type.ReferenceType> throwsList = method.getThrownExceptions();
boolean hasExecutionException = false;
boolean hasFailureException = false;
// First check if we have both exception types
for (com.github.javaparser.ast.type.ReferenceType type : throwsList) {
String typeName = type.toString();
if (typeName.contains("MojoExecutionException")) {
hasExecutionException = true;
} else if (typeName.contains("MojoFailureException")) {
hasFailureException = true;
}
}
// Remove both exception types if present and add MojoException once
if (hasExecutionException || hasFailureException) {
throwsList.removeIf(type ->
type.toString().contains("MojoExecutionException") ||
type.toString().contains("MojoFailureException")
);
method.addThrownException(new ClassOrInterfaceType(null, "org.apache.maven.api.plugin.MojoException"));
}
});
});
return modified;
}
private boolean updateMojoAnnotations(CompilationUnit cu) {
boolean modified = false;
for (ClassOrInterfaceDeclaration classDecl : cu.findAll(ClassOrInterfaceDeclaration.class)) {
for (NormalAnnotationExpr anno : classDecl.findAll(NormalAnnotationExpr.class)) {
if (anno.getNameAsString().equals("Mojo")) {
Optional<ImportDeclaration> mojoImport = cu.getImports().stream()
.filter(imp -> imp.getNameAsString().endsWith(".Mojo"))
.findFirst();
if (mojoImport.isPresent() &&
mojoImport.get().getNameAsString().equals("org.apache.maven.plugins.annotations.Mojo")) {
System.out.println("DEBUG: Processing Maven Mojo annotation");
// Check thread safety
boolean isThreadSafe = anno.getPairs().stream()
.filter(pair -> pair.getNameAsString().equals("threadSafe"))
.map(pair -> pair.getValue().toString())
.map(val -> val.equals("true"))
.findFirst()
.orElse(false);
if (!isThreadSafe) {
// Add warning to class JavaDoc
addThreadSafetyWarning(classDecl);
modified = true;
}
// Remove threadSafe attribute
anno.getPairs().removeIf(pair -> pair.getNameAsString().equals("threadSafe"));
// Update defaultPhase if present
for (MemberValuePair pair : anno.getPairs()) {
if (pair.getNameAsString().equals("defaultPhase")) {
String phase = pair.getValue().toString();
// Clean up the phase string (remove quotes and LifecyclePhase. prefix if present)
phase = phase.replaceAll("\"", "")
.replaceAll("LifecyclePhase\\.", "");
if (PHASE_REPLACEMENTS.containsKey(phase)) {
String newPhase = PHASE_REPLACEMENTS.get(phase);
String phaseExpr;
if (newPhase.contains("+")) {
String[] parts = newPhase.split("\\s*\\+\\s*");
phaseExpr = String.format("org.apache.maven.api.Lifecycle.%s + org.apache.maven.api.Lifecycle.Phase.%s",
parts[0].trim(), parts[1].trim());
} else {
phaseExpr = "org.apache.maven.api.Lifecycle.Phase." + newPhase;
}
// Create a new name expression instead of string literal
pair.setValue(new NameExpr(phaseExpr));
modified = true;
}
break;
}
}
}
}
}
}
return modified;
}
private boolean updateExceptionConstructions(CompilationUnit cu) {
boolean modified = false;
// Find all object creation expressions
for (ObjectCreationExpr objCreation : cu.findAll(ObjectCreationExpr.class)) {
String exceptionName = objCreation.getType().getNameAsString();
if (EXCEPTION_REPLACEMENTS.containsKey(exceptionName)) {
// Get the current arguments
NodeList<Expression> arguments = objCreation.getArguments();
// Create new MojoException with the same arguments
ObjectCreationExpr newException = new ObjectCreationExpr();
newException.setType("org.apache.maven.api.plugin.MojoException");
// Transfer all arguments to the new exception
newException.setArguments(arguments);
// Replace the old exception creation with the new one
objCreation.replace(newException);
modified = true;
// Make sure the import is updated
updateImportForException(cu);
}
}
return modified;
}
private void updateImportForException(CompilationUnit cu) {
// Remove old exception imports
cu.getImports().removeIf(importDecl ->
importDecl.getNameAsString().endsWith("MojoExecutionException") ||
importDecl.getNameAsString().endsWith("MojoFailureException")
);
// Add new MojoException import if not already present
String newImport = "org.apache.maven.api.plugin.MojoException";
boolean hasNewImport = cu.getImports().stream()
.anyMatch(importDecl -> importDecl.getNameAsString().equals(newImport));
if (!hasNewImport) {
cu.addImport(newImport);
}
}
private void addThreadSafetyWarning(ClassOrInterfaceDeclaration classDecl) {
String warning = "TODO: Maven 4 plugins need to be thread safe. Please verify and fix thread safety issues.";
// Get or create JavaDoc
if (classDecl.getJavadoc().isPresent()) {
String existingDoc = classDecl.getJavadoc().get().getDescription().toText();
if (!existingDoc.contains(warning)) {
classDecl.setJavadocComment(existingDoc + "\n\n" + warning);
}
} else {
classDecl.setJavadocComment(warning);
}
}
private Optional<String> resolveAnnotationPackage(Node node) {
// Find the import declaration for this annotation
return node.findCompilationUnit()
.flatMap(cu -> cu.getImports().stream()
.filter(imp -> imp.getNameAsString().endsWith("." + node.toString()))
.findFirst()
.map(imp -> imp.getNameAsString().substring(0,
imp.getNameAsString().lastIndexOf(".")))
);
}
private boolean isFromPackage(Node node, String expectedPackage) {
return resolveAnnotationPackage(node)
.map(pkg -> pkg.equals(expectedPackage))
.orElse(false);
}
private boolean updateDependencyInjection(CompilationUnit cu) {
boolean modified = false;
// Replace Component annotations with Inject
for (MarkerAnnotationExpr anno : cu.findAll(MarkerAnnotationExpr.class)) {
if (anno.getNameAsString().equals("Component") &&
isFromPackage(anno, "org.apache.maven.plugins.annotations")) {
anno.setName("Inject");
modified = true;
}
}
return modified;
}
public static void main(String... args) {
int exitCode = new CommandLine(new Maven4Migration()).execute(args);
System.exit(exitCode);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment