io.codemodder.plugins.maven.operator.POMScanner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of codemodder-plugin-maven Show documentation
Show all versions of codemodder-plugin-maven Show documentation
Plugin for providing Maven dependency management functions to codemods.
package io.codemodder.plugins.maven.operator;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The POMScanner class provides methods for scanning Maven POM (Project Object Model) files,
* including the original POM and its parent POMs, to create a ProjectModelFactory. This class
* offers both modern and legacy scanning methods for flexibility.
*/
class POMScanner {
private static final Pattern RE_WINDOWS_PATH = Pattern.compile("^[A-Za-z]:");
private static final String ARTIFACT_ID = "artifactId";
private static final Logger LOGGER = LoggerFactory.getLogger(POMScanner.class);
private final Path originalPath;
private final Path topLevelDirectory;
private Path lastPath;
public POMScanner(final Path originalPath, final Path topLevelDirectory) {
this.originalPath = originalPath;
this.topLevelDirectory = topLevelDirectory;
}
/**
* Scans a POM file and its parent POMs using the legacy method and creates a ProjectModelFactory.
*
* @return A ProjectModelFactory representing the scanned POMs.
* @throws DocumentException If a document error occurs.
* @throws IOException If an I/O error occurs.
* @throws URISyntaxException If there is an issue with the URI syntax.
*/
public ProjectModelFactory scanFrom() throws DocumentException, IOException, URISyntaxException {
POMDocument pomFile = POMDocumentFactory.load(originalPath);
List parentPomFiles = new ArrayList<>();
Queue pomFileQueue = new LinkedList<>();
Element relativePathElement = getRelativePathElement(pomFile);
Element parentElement = getParentElement(pomFile);
processRelativePathElement(
relativePathElement, parentElement, originalPath, topLevelDirectory, pomFileQueue);
lastPath = originalPath;
Set prevPaths = new HashSet<>();
POMDocument prevPOMDocument = pomFile;
processPOMFileQueue(pomFileQueue, prevPaths, parentPomFiles, prevPOMDocument, pomFile);
return ProjectModelFactory.loadFor(pomFile, parentPomFiles);
}
private void processPOMFileQueue(
Queue pomFileQueue,
Set prevPaths,
List parentPomFiles,
POMDocument prevPOMDocument,
POMDocument pomFile)
throws DocumentException, IOException, URISyntaxException {
while (!pomFileQueue.isEmpty()) {
Element currentRelativePathElement = pomFileQueue.poll();
if (StringUtils.isEmpty(currentRelativePathElement.getTextTrim())) {
break;
}
String relativePath = fixPomRelativePath(currentRelativePathElement.getText());
if (!processRelativePath(relativePath, prevPaths, pomFile)) {
break;
}
Path newPath = resolvePath(lastPath, relativePath);
if (!validateNewPath(newPath, topLevelDirectory)) {
break;
}
POMDocument newPomFile = POMDocumentFactory.load(newPath);
processParentAndRelativePathElements(newPomFile);
if (!checkArtifactIdMatch(newPomFile, prevPOMDocument)) {
break;
}
parentPomFiles.add(newPomFile);
prevPOMDocument = newPomFile;
Element newRelativePathElement = getRelativePathElement(newPomFile);
if (newRelativePathElement != null) {
pomFileQueue.add(newRelativePathElement);
}
}
}
private Element getParentElement(POMDocument pomFile) {
return pomFile.getPomDocument().getRootElement().element("parent");
}
private Element getRelativePathElement(POMDocument pomFile) {
Element parentElement = getParentElement(pomFile);
return (parentElement != null) ? parentElement.element("relativePath") : null;
}
private String getParentArtifactId(POMDocument prevPOMDocument) {
Element parentElement = getParentElement(prevPOMDocument);
return (parentElement != null) ? parentElement.element(ARTIFACT_ID).getText() : null;
}
private void processRelativePathElement(
Element relativePathElement,
Element parentElement,
Path originalPath,
Path topLevelDirectory,
Queue pomFileQueue) {
if (relativePathElement != null && StringUtils.isNotEmpty(relativePathElement.getTextTrim())) {
pomFileQueue.add(relativePathElement);
} else if (relativePathElement == null
&& parentElement != null
&& isNotRoot(originalPath, topLevelDirectory)) {
addDefaultRelativePathElement(pomFileQueue);
}
}
private boolean isNotRoot(Path originalPath, Path topLevelDirectory) {
return !originalPath.getParent().equals(topLevelDirectory);
}
private DefaultElement createRelativePathElement() {
DefaultElement newRelativePathElement = new DefaultElement("relativePath");
newRelativePathElement.setText("../pom.xml");
return newRelativePathElement;
}
private void addDefaultRelativePathElement(Queue pomFileQueue) {
DefaultElement newRelativePathElement = createRelativePathElement();
pomFileQueue.add(newRelativePathElement);
}
private void processParentAndRelativePathElements(POMDocument newPomFile) {
boolean hasParent = getParentElement(newPomFile) != null;
boolean hasRelativePath = getRelativePathElement(newPomFile) != null;
if (!hasRelativePath && hasParent) {
Element parentElement = newPomFile.getPomDocument().getRootElement().element("parent");
DefaultElement newRelativePathElement = createRelativePathElement();
parentElement.add(newRelativePathElement);
}
}
private boolean processRelativePath(
String relativePath, Set prevPaths, POMDocument pomFile) {
if (!isRelative(relativePath)) {
LOGGER.debug("not relative: {}", relativePath);
return false;
}
if (prevPaths.contains(relativePath)) {
LOGGER.debug("loop: {}, relativePath: {}", pomFile.getPath(), relativePath);
return false;
} else {
prevPaths.add(relativePath);
}
return true;
}
private boolean validateNewPath(Path newPath, Path topLevelDirectory) {
try {
if (Files.notExists(newPath)) {
LOGGER.warn("new path does not exist: " + newPath);
return false;
}
if (Files.size(newPath) == 0) {
LOGGER.warn("File has zero length: " + newPath);
return false;
}
if (!newPath.startsWith(topLevelDirectory)) {
LOGGER.warn(
"Not a child: " + newPath + " (absolute: " + topLevelDirectory.toAbsolutePath() + ")");
return false;
}
return true;
} catch (IOException e) {
LOGGER.error("Error while validating path: " + newPath, e);
return false;
}
}
private boolean checkArtifactIdMatch(POMDocument newPomFile, POMDocument prevPOMDocument) {
String myArtifactId =
newPomFile.getPomDocument().getRootElement().element(ARTIFACT_ID) != null
? newPomFile.getPomDocument().getRootElement().element(ARTIFACT_ID).getText()
: null;
String prevParentArtifactId = getParentArtifactId(prevPOMDocument);
if (myArtifactId == null || prevParentArtifactId == null) {
LOGGER.warn(
"Missing previous mine or parent: " + myArtifactId + " / " + prevParentArtifactId);
return false;
}
if (!myArtifactId.equals(prevParentArtifactId)) {
LOGGER.warn("Previous doesn't match: " + myArtifactId + " / " + prevParentArtifactId);
return false;
}
return true;
}
private Path resolvePath(final Path baseFile, final String relativePath) {
Path parentDir = baseFile;
if (parentDir != null && !Files.isDirectory(parentDir)) {
parentDir = parentDir.getParent();
}
assert parentDir != null;
Path result = parentDir.resolve(relativePath).normalize().toAbsolutePath();
lastPath = Files.isDirectory(result) ? result : result.getParent();
return result;
}
private String fixPomRelativePath(final String text) {
if (text == null) {
return "";
}
String name = new File(text).getName();
if (name.indexOf('.') == -1) {
return text + "/pom.xml";
}
return text;
}
private boolean isRelative(final String path) {
if (RE_WINDOWS_PATH.matcher(path).matches()) {
return false;
}
return !(path.startsWith("/") || path.startsWith("~"));
}
}