diff --git a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
index 1218c47e..918c53d5 100644
--- a/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
+++ b/src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java
@@ -369,6 +369,16 @@ 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.
+ * Default value is false (= not re-adding comments).
+ *
+ * @since 1.3.0
+ */
+ @Parameter( property = "flatten.dependency.keepComments", required = false , defaultValue = "false")
+ private boolean keepCommentsInPom;
+
@Component
private DependencyResolver dependencyResolver;
@@ -399,11 +409,15 @@ public void execute()
getLog().info( "Generating flattened POM of project " + this.project.getId() + "..." );
File originalPomFile = this.project.getFile();
+ KeepCommentsInPom commentsOfOriginalPomFile = null;
+ if (keepCommentsInPom) {
+ 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() )
{
@@ -411,7 +425,9 @@ public void execute()
}
}
- /**
+
+
+ /**
* This method extracts the XML header comment if available.
*
* @param xmlFile is the XML {@link File} to parse.
@@ -447,7 +463,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
{
@@ -485,10 +501,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.
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..c0aa5ed4
--- /dev/null
+++ b/src/main/java/org/codehaus/mojo/flatten/KeepCommentsInPom.java
@@ -0,0 +1,269 @@
+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.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, nodePath) ->
+ {
+ // collectNodesByPathNames
+ commentsPaths.put(nodePath, 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;
+ aConsumer.accept(aNode, tempNodePath);
+ 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 < tempChilds.getLength(); i++)
+ {
+ Node tempItem = tempChilds.item(i);
+ if (tempItem.getNodeType() != Node.TEXT_NODE && tempItem.getNodeType() != Node.COMMENT_NODE)
+ {
+ // Take real nodes to find them back by number
+ String tempChildNodeName = tempItem.getNodeName();
+ Integer tempChildWithSameNameCount = tempChildWithSameName.get(tempChildNodeName);
+ if (tempChildWithSameNameCount == null)
+ {
+ tempChildWithSameNameCount = 1;
+ } else
+ {
+ tempChildWithSameNameCount += 1;
+ }
+ tempChildWithSameName.put(tempChildNodeName, tempChildWithSameNameCount);
+ tempCopiedChilds.add(tempItem);
+ }
+ }
+ Map tempChildWithSameNameCounters = new HashMap<>();
+ for (int i = 0; i < tempCopiedChilds.size(); i++)
+ {
+ Node tempCopiedChild = tempCopiedChilds.get(i);
+ String tempChildNodeName = tempCopiedChild.getNodeName();
+ if (tempChildWithSameName.get(tempChildNodeName) > 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, nodePath) ->
+ {
+ Node tempOriginalNode = commentsPaths.get(nodePath);
+ 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
new file mode 100644
index 00000000..b00df593
--- /dev/null
+++ b/src/test/java/org/codehaus/mojo/flatten/KeepCommentsInPomTest.java
@@ -0,0 +1,125 @@
+package org.codehaus.mojo.flatten;
+
+import static org.junit.Assert.*;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.plugin.testing.MojoRule;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.configuration.DefaultPlexusConfiguration;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test-Case for {@link FlattenMojo}.
+ *
+ */
+public class KeepCommentsInPomTest
+{
+
+ private static final String PATH = "src/test/resources/keep-comments-in-pom/";
+ private static final String TEST_TARGET_PATH = "target/test/resources/keep-comments-in-pom/";
+ private static final String FLATTENED_POM = TEST_TARGET_PATH + ".flattened-pom.xml";
+ private static final String EXPECTED_FLATTENED_POM = PATH + "expected-flattened-pom.xml";
+ /**
+ * Expected result since jdk11 with updated xml header and properties sequence.
+ */
+ private static final String EXPECTED_FLATTENED_POM_JDK11 = PATH + "expected-flattened-pom-jdk11.xml";
+
+ @Rule
+ public MojoRule rule = new MojoRule();
+
+ @Before
+ public void setup()
+ {
+ new File(TEST_TARGET_PATH).mkdirs();
+ }
+
+ /**
+ * Test method to check that profile activation file is not interpolated.
+ *
+ * @throws Exception if something goes wrong.
+ */
+ @Test
+ public void keepsProfileActivationFile() throws Exception
+ {
+ MavenProject project = rule.readMavenProject(new File(PATH));
+ FlattenMojo flattenMojo = (FlattenMojo) rule.lookupConfiguredMojo(project, "flatten");
+
+ DefaultPlexusConfiguration tempPluginConfiguration = new DefaultPlexusConfiguration("test");
+ tempPluginConfiguration.addChild("outputDirectory", TEST_TARGET_PATH);
+ tempPluginConfiguration.addChild("keepCommentsInPom", "true");
+ rule.configureMojo(flattenMojo, tempPluginConfiguration);
+
+ // execute writes new FLATTENED_POM
+ flattenMojo.execute();
+
+ String tempExpectedContent;
+ if (isJdk8())
+ {
+ tempExpectedContent = getContent(EXPECTED_FLATTENED_POM);
+ } else
+ {
+ tempExpectedContent = getContent(EXPECTED_FLATTENED_POM_JDK11);
+ }
+ String tempActualContent = getContent(FLATTENED_POM);
+ assertEquals("Expected POM does not match, see " + FLATTENED_POM, tempExpectedContent, tempActualContent);
+
+ }
+
+ /**
+ * Check runtime version.
+ *
+ * @return true when runtime is JDK11
+ */
+ private boolean isJdk8()
+ {
+ // With Java 9 can be switched to java.lang.Runtime.version()
+ String tempPropertyVersion = System.getProperty("java.version");
+ if (tempPropertyVersion.startsWith("1.8."))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ *
+ */
+ private String getContent(String aPomFile) throws IOException
+ {
+ String tempString;
+ try (InputStream tempIn = new FileInputStream(aPomFile))
+ {
+ tempString = IOUtils.toString(tempIn);
+ }
+ // remove platform dependent CR/LF
+ tempString = tempString.replaceAll("\r\n", "\n");
+ return tempString;
+ }
+
+}
diff --git a/src/test/resources/keep-comments-in-pom/expected-flattened-pom-jdk11.xml b/src/test/resources/keep-comments-in-pom/expected-flattened-pom-jdk11.xml
new file mode 100644
index 00000000..b8c740d6
--- /dev/null
+++ b/src/test/resources/keep-comments-in-pom/expected-flattened-pom-jdk11.xml
@@ -0,0 +1,57 @@
+
+ 4.0.0
+ org.codehaus.mojo.flatten.its
+ resolve-properties-ci-do-not-interpolate-profile-activation-file
+ 1.2.3.4
+
+ test-propertyWithoutComment
+
+
+ test-propertyWithTwoComments
+
+ 1.2.3.4
+
+
+ verify
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+
+ 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/expected-flattened-pom.xml b/src/test/resources/keep-comments-in-pom/expected-flattened-pom.xml
new file mode 100644
index 00000000..6c5543bd
--- /dev/null
+++ b/src/test/resources/keep-comments-in-pom/expected-flattened-pom.xml
@@ -0,0 +1,58 @@
+
+
+ 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
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+
+ 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
new file mode 100644
index 00000000..72a37723
--- /dev/null
+++ b/src/test/resources/keep-comments-in-pom/pom.xml
@@ -0,0 +1,62 @@
+
+ 4.0.0
+ org.codehaus.mojo.flatten.its
+ resolve-properties-ci-do-not-interpolate-profile-activation-file
+ ${revision}
+
+
+
+ 1.2.3.4
+ test-propertyWithoutComment
+
+
+ test-propertyWithTwoComments
+
+
+
+ verify
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+
+ resolveCiFriendliesOnly
+
+
+
+
+ maven-surefire-plugin
+
+
+ **/TestCircle.java
+ **/TestCircle2.java
+
+ **/TestSquare.java
+
+ **/TestSquare2.java
+
+
+
+
+
+
+
+
+
+
+
+ file.txt
+
+
+
+
+ multiline-profile
+
+
+
+
+
+