From d7aa7579563836f8be188e497a78eacb3f8be64b Mon Sep 17 00:00:00 2001
From: Stefan CORDES <50696194+ca-stefan-cordes@users.noreply.github.com>
Date: Fri, 8 Apr 2022 19:03:56 +0200
Subject: [PATCH] keep comments in .flattened-pom.xml #269
---
.../codehaus/mojo/flatten/FlattenMojo.java | 46 +++-
.../mojo/flatten/KeepCommentsInPom.java | 236 ++++++++++++++++++
.../mojo/flatten/KeepCommentsInPomTest.java | 1 +
.../expected-flattened-pom.xml | 34 ++-
.../resources/keep-comments-in-pom/pom.xml | 26 ++
5 files changed, 333 insertions(+), 10 deletions(-)
create mode 100644 src/main/java/org/codehaus/mojo/flatten/KeepCommentsInPom.java
diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index 4042f6cf..03853872 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -356,6 +356,13 @@ public class FlattenMojo
@Parameter( defaultValue = "${session}", readonly = true, required = true )
private MavenSession session;
+ /**
+ * The core maven model readers/writers are discarding the comments of the pom.xml.
+ * By setting keepCommentsInPom to true the current comments are moved to the flattened pom.xml.
+ */
+ @Parameter( property = "flatten.dependency.keepComments", required = false )
+ private Boolean keepCommentsInPom;
+
@Component
private DependencyResolver dependencyResolver;
@@ -386,11 +393,15 @@ public void execute()
getLog().info( "Generating flattened POM of project " + this.project.getId() + "..." );
File originalPomFile = this.project.getFile();
+ KeepCommentsInPom commentsOfOriginalPomFile = null;
+ if (isKeepCommentsInPom()) {
+ commentsOfOriginalPomFile = KeepCommentsInPom.create(getLog(), originalPomFile);
+ }
Model flattenedPom = createFlattenedPom( originalPomFile );
String headerComment = extractHeaderComment( originalPomFile );
File flattenedPomFile = getFlattenedPomFile();
- writePom( flattenedPom, flattenedPomFile, headerComment );
+ writePom( flattenedPom, flattenedPomFile, headerComment , commentsOfOriginalPomFile);
if ( isUpdatePomFile() )
{
@@ -398,7 +409,9 @@ public void execute()
}
}
- /**
+
+
+ /**
* This method extracts the XML header comment if available.
*
* @param xmlFile is the XML {@link File} to parse.
@@ -434,7 +447,7 @@ protected String extractHeaderComment( File xmlFile )
* before root tag). May be null
if not present and to be omitted in target POM.
* @throws MojoExecutionException if the operation failed (e.g. due to an {@link IOException}).
*/
- protected void writePom( Model pom, File pomFile, String headerComment )
+ protected void writePom( Model pom, File pomFile, String headerComment, KeepCommentsInPom anOriginalCommentsPath )
throws MojoExecutionException
{
@@ -472,10 +485,16 @@ protected void writePom( Model pom, File pomFile, String headerComment )
getLog().warn( "POM XML post-processing failed: no project tag found!" );
}
}
- writeStringToFile( buffer.toString(), pomFile, pom.getModelEncoding() );
+ String xmlString;
+ if (anOriginalCommentsPath == null) {
+ xmlString = buffer.toString();
+ } else {
+ xmlString = anOriginalCommentsPath.restoreOriginalComments(buffer.toString(), pom.getModelEncoding());
+ }
+ writeStringToFile( xmlString, pomFile, pom.getModelEncoding() );
}
- /**
+ /**
* Writes the given data
to the given file
using the specified encoding
.
*
* @param data is the {@link String} to write.
@@ -1213,6 +1232,23 @@ public boolean isUpdatePomFile()
}
}
+ /**
+ * @return true
if the generated flattened POM shall have the comments of the original file
+ * false
will remove the comments.
+ */
+ public boolean isKeepCommentsInPom()
+ {
+
+ if ( this.keepCommentsInPom == null )
+ {
+ return false;
+ }
+ else
+ {
+ return this.keepCommentsInPom.booleanValue();
+ }
+ }
+
/**
* This class is a simple SAX handler that extracts the first comment located before the root tag in an XML
* document.
diff --git a/src/main/java/org/codehaus/mojo/flatten/KeepCommentsInPom.java b/src/main/java/org/codehaus/mojo/flatten/KeepCommentsInPom.java
new file mode 100644
index 00000000..8b02303f
--- /dev/null
+++ b/src/main/java/org/codehaus/mojo/flatten/KeepCommentsInPom.java
@@ -0,0 +1,236 @@
+package org.codehaus.mojo.flatten;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.bootstrap.DOMImplementationRegistry;
+import org.w3c.dom.ls.DOMImplementationLS;
+import org.w3c.dom.ls.LSOutput;
+import org.w3c.dom.ls.LSSerializer;
+import org.xml.sax.SAXException;
+
+/**
+ * Helper class to keep the comments how they have been in the original pom.xml
+ * While reading with {@link org.apache.maven.model.io.xpp3.MavenXpp3Writer} the comments are not placed into the {@link org.apache.maven.model.Model}
+ * and so {@link org.apache.maven.model.io.xpp3.MavenXpp3Writer} is not able to re-write those comments.
+ *
+ * Workaround (maybe until core is fixed) is to remember all the comments and restore them after MavenXpp3Writer has created the new flattened pom.xml.
+ *
+ * Current restriction on non-unique child nodes is that this class finds the node back due to the position in the file,
+ * that may lead to mis-re-added comments e.g. on multiple added dependencies
+ * (but for e.g. resolveCiFriendliesOnly the nodes keep stable)
+ *
+ */
+class KeepCommentsInPom {
+
+ /**
+ * Create an instance with collected current comments of the passed pom.xml file.
+ */
+ static KeepCommentsInPom create(Log aLog, File aOriginalPomFile) throws MojoExecutionException {
+ KeepCommentsInPom tempKeepCommentsInPom = new KeepCommentsInPom();
+ tempKeepCommentsInPom.setLog(aLog);
+ tempKeepCommentsInPom.loadComments(aOriginalPomFile);
+ return tempKeepCommentsInPom;
+ }
+
+ private Log log;
+
+ /**
+ * The unique path list for an original node (the comments are stored via the referenced previousSibling)
+ */
+ private Map> commentsPaths;
+
+
+ /**
+ *
+ */
+ KeepCommentsInPom() {
+ super();
+ }
+
+ /**
+ * load all current comments and text fragments from xml file
+ * @param anOriginalPomFile the pom.xml
+ */
+ private void loadComments(File anOriginalPomFile) throws MojoExecutionException {
+ commentsPaths = new HashMap<>();
+ DocumentBuilderFactory tempDBF = DocumentBuilderFactory.newInstance();
+ DocumentBuilder tempDB;
+ try {
+ tempDB = tempDBF.newDocumentBuilder();
+ Document tempPom = tempDB.parse(anOriginalPomFile);
+ Node tempNode = tempPom.getDocumentElement();
+ walkOverNodes(tempNode,".",(node,nodes)-> {
+ // collectNodesByPathNames
+ nodes.set(node);
+ });
+ } catch (ParserConfigurationException | SAXException | IOException e) {
+ throw new MojoExecutionException("Cannot load comments from "+anOriginalPomFile, e);
+ }
+ }
+
+ /**
+ * Walk over the pom hierarchy of the Document.
+ * @param Node the current Node
+ * @param String the unique path in the parent
+ * @param aConsumer Function to be called with the toBeCollected/found node.
+ */
+ private void walkOverNodes(Node aNode, String aParentPath, BiConsumer> aConsumer) {
+ String tempNodeName = aNode.getNodeName();
+ if (log.isDebugEnabled()) {
+ log.debug("walkOverNodes: aParentPath="+aParentPath+" tempNodeName="+tempNodeName);
+ }
+ String tempNodePath = aParentPath+"\t"+tempNodeName;
+ AtomicReference tempNodesOnPath = commentsPaths.get(tempNodePath);
+ if (tempNodesOnPath == null) {
+ tempNodesOnPath = new AtomicReference<>();
+ commentsPaths.put(tempNodePath,tempNodesOnPath);
+ }
+ aConsumer.accept(aNode, tempNodesOnPath);
+ NodeList tempChilds = aNode.getChildNodes();
+ // Copy the childs as aConsumer may change the node sequence (add a comment)
+ List tempCopiedChilds = new ArrayList<>();
+ Map tempChildWithSameName = new HashMap<>();
+ for (int i=0;i tempChildWithSameNameCounters = new HashMap<>();
+ for (int i=0;i 1) {
+ Integer tempChildWithSameNameCounter = tempChildWithSameNameCounters.get(tempChildNodeName);
+ if (tempChildWithSameNameCounter == null) {
+ tempChildWithSameNameCounter = 1;
+ } else {
+ tempChildWithSameNameCounter += 1;
+ }
+ tempChildWithSameNameCounters.put(tempChildNodeName,tempChildWithSameNameCounter);
+ // add a counter to find back the correct node.
+ walkOverNodes(tempCopiedChild, tempNodePath+"\t"+tempChildWithSameNameCounter, aConsumer);
+ } else {
+ // unique child names
+ walkOverNodes(tempCopiedChild, tempNodePath, aConsumer);
+ }
+ }
+ }
+
+
+ /**
+ * @param String the XML written by {@link org.apache.maven.model.io.xpp3.MavenXpp3Writer}
+ */
+ public String restoreOriginalComments(String anXml, String aModelEncoding) throws MojoExecutionException {
+ DocumentBuilderFactory tempDBF = DocumentBuilderFactory.newInstance();
+ DocumentBuilder tempDB;
+ try {
+ tempDB = tempDBF.newDocumentBuilder();
+ String tempEncoding = aModelEncoding==null ? "UTF-8" : aModelEncoding; // default encoding UTF-8 when nothing in pom model.
+ Document tempPom = tempDB.parse(new ByteArrayInputStream(anXml.getBytes(tempEncoding)));
+ Node tempNode = tempPom.getDocumentElement();
+ walkOverNodes(tempNode,".",(newNode,originalNodeRef)->{
+ Node tempOriginalNode = originalNodeRef.get();
+ if (tempOriginalNode != null) {
+ String tempOriginalNodeName = tempOriginalNode.getNodeName();
+ if (tempOriginalNodeName.equals(newNode.getNodeName())) {
+ // found matching node
+ Node tempRefChild = newNode;
+ Node tempPotentialCommentOrText = tempOriginalNode.getPreviousSibling();
+ while (tempPotentialCommentOrText != null && tempPotentialCommentOrText.getNodeType() == Node.TEXT_NODE) {
+ // skip text in the original xml node
+ tempPotentialCommentOrText = tempPotentialCommentOrText.getPreviousSibling();
+ }
+ while (tempPotentialCommentOrText != null && tempPotentialCommentOrText.getNodeType() == Node.COMMENT_NODE) {
+ // copy the node to be able to call previoussibling for next element
+ Node tempRefPrevious = tempRefChild.getPreviousSibling();
+ String tempWhitespaceTextBeforeRefNode = null;
+ if (tempRefPrevious != null && tempRefPrevious.getNodeType()==Node.TEXT_NODE) {
+ tempWhitespaceTextBeforeRefNode = tempRefPrevious.getNodeValue();
+ }
+ Node tempNewComment;
+ tempNewComment = tempPom.createComment(tempPotentialCommentOrText.getNodeValue());
+ tempRefChild.getParentNode().insertBefore(tempNewComment, tempRefChild);
+ // copy the whitespaces between comment and refNode
+ if (tempWhitespaceTextBeforeRefNode!=null) {
+ tempRefChild.getParentNode().insertBefore(tempPom.createTextNode(tempWhitespaceTextBeforeRefNode), tempRefChild);
+ }
+
+ tempRefChild=tempNewComment;
+
+ tempPotentialCommentOrText = tempPotentialCommentOrText.getPreviousSibling();
+ while (tempPotentialCommentOrText != null && tempPotentialCommentOrText.getNodeType() == Node.TEXT_NODE) {
+ // skip text in the original xml node
+ tempPotentialCommentOrText = tempPotentialCommentOrText.getPreviousSibling();
+ }
+ }
+ }
+ }
+ });
+ return writeDocumentToString(tempPom);
+ } catch (ParserConfigurationException | SAXException | IOException | ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException e) {
+ throw new MojoExecutionException("Cannot add comments", e);
+ }
+ }
+
+ /**
+ * Use an LSSerializer to keep whitespaces added by MavenXpp3Writer
+ * @param Document the pom to write to String.
+ */
+ private String writeDocumentToString(Document aPom) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
+ DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
+ LSOutput output = impl.createLSOutput();
+ output.setEncoding("UTF-8");
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ output.setByteStream(outStream );
+ LSSerializer writer = impl.createLSSerializer();
+ writer.write(aPom, output);
+ return new String(outStream.toByteArray());
+ }
+
+ /**
+ * @see #log
+ */
+ public Log getLog() {
+ return log;
+ }
+
+ /**
+ * @see #log
+ */
+ public void setLog(Log aLog) {
+ log = aLog;
+ }
+
+
+
+
+}
diff --git a/src/test/java/org/codehaus/mojo/flatten/KeepCommentsInPomTest.java b/src/test/java/org/codehaus/mojo/flatten/KeepCommentsInPomTest.java
index 8fc03ab1..400d63b8 100644
--- a/src/test/java/org/codehaus/mojo/flatten/KeepCommentsInPomTest.java
+++ b/src/test/java/org/codehaus/mojo/flatten/KeepCommentsInPomTest.java
@@ -64,6 +64,7 @@ public void keepsProfileActivationFile() throws Exception {
DefaultPlexusConfiguration tempPluginConfiguration = new DefaultPlexusConfiguration("test");
tempPluginConfiguration.addChild("outputDirectory", TEST_TARGET_PATH);
+ tempPluginConfiguration.addChild("keepCommentsInPom", "true");
rule.configureMojo(flattenMojo, tempPluginConfiguration);
// execute writes new FLATTENED_POM
diff --git a/src/test/resources/keep-comments-in-pom/expected-flattened-pom.xml b/src/test/resources/keep-comments-in-pom/expected-flattened-pom.xml
index 83742f89..6c5543bd 100644
--- a/src/test/resources/keep-comments-in-pom/expected-flattened-pom.xml
+++ b/src/test/resources/keep-comments-in-pom/expected-flattened-pom.xml
@@ -1,13 +1,16 @@
-
+
4.0.0
org.codehaus.mojo.flatten.its
resolve-properties-ci-do-not-interpolate-profile-activation-file
1.2.3.4
-
+
1.2.3.4
+ test-propertyWithoutComment
+
+
+ test-propertyWithTwoComments
verify
@@ -19,16 +22,37 @@
resolveCiFriendliesOnly
+
+
+ maven-surefire-plugin
+
+
+ **/TestCircle.java
+ **/TestCircle2.java
+
+ **/TestSquare.java
+
+ **/TestSquare2.java
+
+
+
-
+
file.txt
+
+ multiline-profile
+
+
+
-
+
\ No newline at end of file
diff --git a/src/test/resources/keep-comments-in-pom/pom.xml b/src/test/resources/keep-comments-in-pom/pom.xml
index d1d41a08..72a37723 100644
--- a/src/test/resources/keep-comments-in-pom/pom.xml
+++ b/src/test/resources/keep-comments-in-pom/pom.xml
@@ -8,6 +8,10 @@
1.2.3.4
+ test-propertyWithoutComment
+
+
+ test-propertyWithTwoComments
@@ -20,6 +24,20 @@
resolveCiFriendliesOnly
+
+
+ maven-surefire-plugin
+
+
+ **/TestCircle.java
+ **/TestCircle2.java
+
+ **/TestSquare.java
+
+ **/TestSquare2.java
+
+
+
@@ -32,5 +50,13 @@
+
+ multiline-profile
+
+
+
+