package net.jangaroo.exml.mojo.pom;
import org.apache.maven.plugin.MojoExecutionException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION;
import static javax.xml.xpath.XPathConstants.NODE;
import static org.apache.commons.io.FileUtils.readFileToString;
import static org.apache.commons.lang.StringUtils.repeat;
public class PomConverter {
/**
* Replaces exml-maven-plugin configuration by jangaroo-maven-plugin configuration in a POM within the given
* directory.
*/
public static void removeExmlPlugin(File projectBaseDir) throws MojoExecutionException {
Document document = readPom(projectBaseDir);
removeExmlPlugin(document);
writePom(document, projectBaseDir);
}
/**
* Change the packaging from jangaroo to jangaroo-pkg in a POM within the given directory.
*/
public static void changePackaging(File projectBaseDir) throws MojoExecutionException {
Document document = readPom(projectBaseDir);
changePackaging(document);
writePom(document, projectBaseDir);
}
/**
* Parses a POM from a given directory into a DOM document.
*/
private static Document readPom(File projectBaseDir) throws MojoExecutionException {
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
return documentBuilder.parse(new File(projectBaseDir, "pom.xml"));
} catch (IOException e) {
throw new MojoExecutionException("error while generating modified POM", e);
} catch (ParserConfigurationException e) {
throw new MojoExecutionException("error while generating modified POM", e);
} catch (SAXException e) {
throw new MojoExecutionException("error while generating modified POM", e);
}
}
/**
* Serializes the given DOM document into a POM file within the given directory.
*/
private static void writePom(Document document, File projectBaseDir) throws MojoExecutionException {
try {
File pomFile = new File(projectBaseDir, "pom.xml");
// keep trailing whitespace because it's not reproduced by the transformer and we want to keep the diff small
String pom = readFileToString(pomFile);
String trailingWhitespace = pom.substring(pom.lastIndexOf('>') + 1);
PrintWriter pomWriter = new PrintWriter(pomFile);
try {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
// the transformer does not reproduce the new line after the XML declaration, so we do it on our own
// see https://bugs.openjdk.java.net/browse/JDK-7150637
transformer.setOutputProperty(OMIT_XML_DECLARATION, "yes");
if (document.getXmlEncoding() != null) {
pomWriter.print("<?xml version=\"");
pomWriter.print(document.getXmlVersion());
pomWriter.print("\" encoding=\"");
pomWriter.print(document.getXmlEncoding());
pomWriter.println("\"?>");
}
transformer.transform(new DOMSource(document), new StreamResult(pomWriter));
pomWriter.write(trailingWhitespace);
} finally {
pomWriter.close();
}
} catch (IOException e) {
throw new MojoExecutionException("error while generating modified POM", e);
} catch (TransformerException e) {
throw new MojoExecutionException("error while generating modified POM", e);
}
}
/**
* Replaces exml-maven-plugin configuration by jangaroo-maven-plugin configuration within
* {@code /project/build/plugins} and {@code /project/build/pluginManagement/plugins}.
*/
private static void removeExmlPlugin(Document document) throws MojoExecutionException {
try {
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
Node pluginsNode = (Node) xPath.evaluate("/project/build/plugins", document, NODE);
removeExmlPlugin(pluginsNode);
pluginsNode = (Node) xPath.evaluate("/project/build/pluginManagement/plugins", document, NODE);
removeExmlPlugin(pluginsNode);
} catch (XPathException e) {
throw new MojoExecutionException("error while generating modified POM", e);
}
}
/**
* Changes the packaging from jangaroo to jangaroo-pkg in {@code /project/packaging}
*/
private static void changePackaging(Document document) throws MojoExecutionException {
try {
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
Node packagingNode = (Node) xPath.evaluate("/project/packaging[text() = 'jangaroo']", document, NODE);
if (packagingNode != null) {
packagingNode.setTextContent("jangaroo-pkg");
}
} catch (XPathException e) {
throw new MojoExecutionException("error while generating modified POM", e);
}
}
/**
* Replaces exml-maven-plugin configuration by jangaroo-maven-plugin configuration within the given {@code plugins}
* element.
*/
private static void removeExmlPlugin(Node pluginsNode) throws XPathException {
if (pluginsNode == null) {
return;
}
Document document = pluginsNode.getOwnerDocument();
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
Node exmlPluginNode = (Node) xPath.evaluate("plugin[artifactId='exml-maven-plugin']", pluginsNode, NODE);
if (exmlPluginNode == null) {
return;
}
Node jangarooPluginNode = (Node) xPath.evaluate("plugin[artifactId='jangaroo-maven-plugin']", pluginsNode, NODE);
Node exmlVersionNode = (Node) xPath.evaluate("version", exmlPluginNode, NODE);
Node exmlExtensionNode = (Node) xPath.evaluate("extensions", exmlPluginNode, NODE);
Node configClassPackageNode = (Node) xPath.evaluate("configuration/configClassPackage", exmlPluginNode, NODE);
if (jangarooPluginNode == null) {
jangarooPluginNode = document.createElement("plugin");
insertChildWithWhitespace(pluginsNode, jangarooPluginNode, exmlPluginNode);
Node jangarooPluginGroupIdNode = document.createElement("groupId");
jangarooPluginGroupIdNode.setTextContent("net.jangaroo");
insertChildWithWhitespace(jangarooPluginNode, jangarooPluginGroupIdNode, null);
Node jangarooPluginArtifactIdNode = document.createElement("artifactId");
jangarooPluginArtifactIdNode.setTextContent("jangaroo-maven-plugin");
insertChildWithWhitespace(jangarooPluginNode, jangarooPluginArtifactIdNode, null);
if (exmlVersionNode != null) {
Node jangarooPluginVersionNode = document.createElement("version");
jangarooPluginVersionNode.setTextContent(exmlVersionNode.getTextContent());
insertChildWithWhitespace(jangarooPluginNode, jangarooPluginVersionNode, null);
}
}
Node jangarooConfigurationNode = (Node) xPath.evaluate("configuration", jangarooPluginNode, NODE);
Node jangarooExtensionsNode = (Node) xPath.evaluate("extensions", jangarooPluginNode, NODE);
removeChildWithWhitespace(pluginsNode, exmlPluginNode);
if (exmlExtensionNode != null) {
if (jangarooExtensionsNode == null) {
insertChildWithWhitespace(jangarooPluginNode, exmlExtensionNode, jangarooConfigurationNode);
} else {
jangarooExtensionsNode.setTextContent(exmlExtensionNode.getTextContent());
}
}
if (configClassPackageNode != null) {
if (jangarooConfigurationNode == null) {
jangarooConfigurationNode = document.createElement("configuration");
insertChildWithWhitespace(jangarooPluginNode, jangarooConfigurationNode, null);
}
Node namespacesNode = document.createElement("namespaces");
insertChildWithWhitespace(jangarooConfigurationNode, namespacesNode, null);
Node namespaceNode = document.createElement("namespace");
insertChildWithWhitespace(namespacesNode, namespaceNode, null);
Node uriNode = document.createElement("uri");
uriNode.setTextContent("exml:" + configClassPackageNode.getTextContent());
insertChildWithWhitespace(namespaceNode, uriNode, null);
}
}
/**
* Removes a child node. Also removed preceding whitespace to preserve correct indentation.
*/
private static void removeChildWithWhitespace(Node parent, Node child) {
Node previousSibling = child.getPreviousSibling();
if (previousSibling.getNodeType() == Node.TEXT_NODE) {
parent.removeChild(previousSibling);
}
parent.removeChild(child);
}
/**
* Insert a child node at a given position. Adds whitespace before and/or after the child if necessary to preserve
* correct indentation..
*/
private static void insertChildWithWhitespace(Node parent, Node child, Node refChild) {
if (refChild == null) {
// trying to append a child
if (parent.getLastChild() != null && parent.getLastChild().getNodeType() == Node.TEXT_NODE) {
// append before the last child node which contains the indentation for the closing tag
refChild = parent.getLastChild();
}
} else if (refChild.getPreviousSibling() != null && refChild.getPreviousSibling().getNodeType() == Node.TEXT_NODE) {
refChild = refChild.getPreviousSibling();
}
parent.insertBefore(child, refChild);
// insert whitespace before child for correct indentation
parent.insertBefore(getIndent(child), child);
if (parent.getChildNodes().getLength() == 2) {
// child was the first child, insert whitespace between child and closing tag for indentation of closing tag
parent.insertBefore(getIndent(parent), null);
}
}
/**
* Creates a text node containing the necessary whitespace to correctly indent the given node.
*/
private static Node getIndent(Node node) {
int depth = getDepth(node);
String whitespace = "\n" + repeat(" ", (depth - 1) * guessIndent(node.getOwnerDocument()));
return node.getOwnerDocument().createTextNode(whitespace);
}
/**
* Calculates the depth of the given node within the document.
*/
private static int getDepth(Node node) {
if (node.getParentNode() == null) {
return 0;
}
return getDepth(node.getParentNode()) + 1;
}
/**
* Tries to guess the document's indentation from the first child element of the document element.
*/
private static int guessIndent(Document document) {
// find first child element
Node firstElement = document.getDocumentElement().getFirstChild();
while (firstElement != null && firstElement.getNodeType() != Node.ELEMENT_NODE) {
firstElement = firstElement.getNextSibling();
}
if (firstElement != null) {
// the node before the first element contains the indenting whitespace
Node whitespaceNode = firstElement.getPreviousSibling();
if (whitespaceNode.getNodeType() == Node.TEXT_NODE) {
String text = whitespaceNode.getTextContent();
int pos = Math.max(text.lastIndexOf("\n"), text.lastIndexOf("\r"));
return text.substring(pos + 1).length();
}
}
return 2;
}
}