io.codemodder.plugins.maven.operator.Util 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 com.github.zafarkhaja.semver.Version;
import java.io.File;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.Text;
import org.dom4j.tree.DefaultText;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.dom4j.Dom4jXPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Common Utilities */
class Util {
private Util() {}
private static final Logger LOGGER = LoggerFactory.getLogger(Util.class);
/** Represents a Property Reference - as a regex */
private static final Pattern PROPERTY_REFERENCE_PATTERN = Pattern.compile("^\\$\\{(.*)}$");
/**
* Method that easily allows to add an element inside another while retaining formatting
*
* @param element Element
* @param d ProjectModel / Context
* @param name new element ("tag") name
* @return created element inside `this` object, already indented after and (optionally) before
*/
static Element addIndentedElement(Element element, POMDocument d, String name) {
List contentList = element.content();
int indentLevel = findIndentLevel(element);
String prefix = d.getEndl() + StringUtils.repeat(d.getIndent(), 1 + indentLevel);
String suffix = d.getEndl() + StringUtils.repeat(d.getIndent(), indentLevel);
if (!contentList.isEmpty() && contentList.get(contentList.size() - 1) instanceof Text) {
Text lastElement = (Text) contentList.get(contentList.size() - 1);
if (StringUtils.isWhitespace(lastElement.getText())) {
contentList.remove(contentList.size() - 1);
}
}
contentList.add(new DefaultText(prefix));
Element newElement = element.addElement(name);
contentList.add(new DefaultText(suffix));
d.setDirty(true);
return newElement;
}
/**
* Guesses the current indent level of the nearest nodes
*
* @return indent level
*/
private static int findIndentLevel(Element startingNode) {
int level = 0;
Element node = startingNode;
while (node.getParent() != null) {
level += 1;
node = node.getParent();
}
return level;
}
/** Given a Version Node, upgrades a resulting POM */
static void upgradeVersionNode(
ProjectModel c, Element versionNode, POMDocument pomDocumentHoldingProperty) {
if (c.isUseProperties()) {
String propertyName = propertyName(c, versionNode);
// Define property
upgradeProperty(c, pomDocumentHoldingProperty, propertyName);
versionNode.setText(escapedPropertyName(propertyName));
} else {
if (c.getDependency() != null && c.getDependency().getVersion() != null) {
String nodeText = versionNode.getText();
String trimmedText = (nodeText != null) ? nodeText.trim() : "";
if (!trimmedText.equals(c.getDependency().getVersion())) {
pomDocumentHoldingProperty.setDirty(true);
versionNode.setText(c.getDependency().getVersion());
}
}
}
}
/** Upserts a given property */
private static void upgradeProperty(ProjectModel c, POMDocument d, String propertyName) {
if (d.getResultPom().getRootElement().element("properties") == null) {
addIndentedElement(d.getResultPom().getRootElement(), d, "properties");
}
Element parentPropertyElement = d.getResultPom().getRootElement().element("properties");
if (parentPropertyElement.element(propertyName) == null) {
addIndentedElement(parentPropertyElement, d, propertyName);
} else {
if (!c.isOverrideIfAlreadyExists()) {
Pattern propertyReferencePattern = Pattern.compile("\\$\\{" + propertyName + "}");
Matcher matcher = propertyReferencePattern.matcher(d.getResultPom().asXML());
int numberOfAllCurrentMatches = 0;
while (matcher.find()) {
numberOfAllCurrentMatches++;
}
if (numberOfAllCurrentMatches > 1) {
throw new IllegalStateException(
"Property " + propertyName + " is already defined - and used more than once.");
}
}
}
Element propertyElement = parentPropertyElement.element(propertyName);
String propertyText =
(propertyElement.getText() != null) ? propertyElement.getText().trim() : "";
if (c.getDependency() != null
&& c.getDependency().getVersion() != null
&& !propertyText.equals(c.getDependency().getVersion())) {
propertyElement.setText(c.getDependency().getVersion());
d.setDirty(true);
}
}
/** Escapes a Property Name */
private static String escapedPropertyName(String propertyName) {
return "${" + propertyName + "}";
}
/** Creates a property Name */
static String propertyName(ProjectModel c, Element versionNode) {
String version = versionNode.getTextTrim();
if (PROPERTY_REFERENCE_PATTERN.matcher(version).matches()) {
Matcher matcher = PROPERTY_REFERENCE_PATTERN.matcher(version);
if (matcher.find()) {
return matcher.group(1);
}
}
if (c.getDependency() != null) {
c.getDependency().getArtifactId();
return "versions." + c.getDependency().getArtifactId();
}
return "versions.default";
}
/** Identifies if an upgrade is needed */
static boolean findOutIfUpgradeIsNeeded(ProjectModel c, Element versionNode) {
String currentVersionNodeText = resolveVersion(c, versionNode.getText());
Version currentVersion = Version.valueOf(currentVersionNodeText);
Version newVersion = Version.valueOf(c.getDependency().getVersion());
boolean versionsAreIncreasing = newVersion.greaterThan(currentVersion);
return versionsAreIncreasing;
}
private static String resolveVersion(ProjectModel c, String versionText) {
if (PROPERTY_REFERENCE_PATTERN.matcher(versionText).matches()) {
StrSubstitutor substitutor = new StrSubstitutor(c.resolvedProperties());
String resolvedVersion = substitutor.replace(versionText);
return resolvedVersion;
} else {
return versionText;
}
}
/**
* Builds a Lookup Expression String for a given dependency
*
* @param dependency Dependency
*/
static String buildLookupExpressionForDependency(Dependency dependency) {
return "/m:project"
+ "/m:dependencies"
+ "/m:dependency"
+ "[./m:groupId[text()='"
+ dependency.getGroupId()
+ "'] and "
+ "./m:artifactId[text()='"
+ dependency.getArtifactId()
+ "']]";
}
/**
* Builds a Lookup Expression String for a given dependency, but under the
* >dependencyManagement> section
*
* @param dependency Dependency
*/
static String buildLookupExpressionForDependencyManagement(Dependency dependency) {
return "/m:project"
+ "/m:dependencyManagement"
+ "/m:dependencies"
+ "/m:dependency"
+ "[./m:groupId[text()='"
+ dependency.getGroupId()
+ "'] and "
+ "./m:artifactId[text()='"
+ dependency.getArtifactId()
+ "']]";
}
static File which(String path) {
List nativeExecutables;
if (SystemUtils.IS_OS_WINDOWS) {
nativeExecutables = new ArrayList<>();
nativeExecutables.add("");
nativeExecutables.add(".exe");
nativeExecutables.add(".bat");
nativeExecutables.add(".cmd");
nativeExecutables.replaceAll(ext -> path + ext);
} else {
nativeExecutables = Arrays.asList(path);
}
String pathContentString = System.getenv("PATH");
String[] pathElements = pathContentString.split(File.pathSeparator);
List possiblePaths = new ArrayList<>();
for (String executable : nativeExecutables) {
for (String pathElement : pathElements) {
possiblePaths.add(new File(new File(pathElement), executable));
}
}
Predicate isCliCallable =
SystemUtils.IS_OS_WINDOWS
? it -> it.exists() && it.isFile()
: it -> it.exists() && it.isFile() && it.canExecute();
File result =
possiblePaths.stream()
.filter(isCliCallable)
.reduce((first, second) -> second) // Find last
.orElse(null);
if (result == null) {
LOGGER.warn(
"Unable to find mvn executable (execs: {}, path: {})",
String.join("/", nativeExecutables),
pathContentString);
}
return result;
}
/**
* Extension Function to Select the XPath Nodes
*
* @param expression expression to use
*/
static List selectXPathNodes(Node node, String expression) {
try {
return createXPathExpression(expression).selectNodes(node);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** Hard-Coded POM Namespace Map */
private static final SimpleNamespaceContext namespaceContext;
static {
Map namespaces = new HashMap<>();
namespaces.put("m", "http://maven.apache.org/POM/4.0.0");
namespaceContext = new SimpleNamespaceContext(namespaces);
}
/**
* Creates a XPath Expression from a given expression string
*
* @param expression expression to create xpath from
*/
private static Dom4jXPath createXPathExpression(String expression) throws Exception {
Dom4jXPath xpath = new Dom4jXPath(expression);
xpath.setNamespaceContext(namespaceContext);
return xpath;
}
}