Skip to content

Commit a940cb1

Browse files
ca-stefan-cordesslawekjaranowski
authored andcommitted
Keep comments in .flattened-pom.xml mojohaus#270
1 parent 13c9a58 commit a940cb1

File tree

6 files changed

+598
-5
lines changed

6 files changed

+598
-5
lines changed

src/main/java/org/codehaus/mojo/flatten/FlattenMojo.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,16 @@ public class FlattenMojo
369369
@Parameter( defaultValue = "${session}", readonly = true, required = true )
370370
private MavenSession session;
371371

372+
/**
373+
* The core maven model readers/writers are discarding the comments of the pom.xml.
374+
* By setting keepCommentsInPom to true the current comments are moved to the flattened pom.xml.
375+
* Default value is false (= not re-adding comments).
376+
*
377+
* @since 1.3.0
378+
*/
379+
@Parameter( property = "flatten.dependency.keepComments", required = false , defaultValue = "false")
380+
private boolean keepCommentsInPom;
381+
372382
@Component
373383
private DependencyResolver dependencyResolver;
374384

@@ -399,19 +409,25 @@ public void execute()
399409
getLog().info( "Generating flattened POM of project " + this.project.getId() + "..." );
400410

401411
File originalPomFile = this.project.getFile();
412+
KeepCommentsInPom commentsOfOriginalPomFile = null;
413+
if (keepCommentsInPom) {
414+
commentsOfOriginalPomFile = KeepCommentsInPom.create(getLog(), originalPomFile);
415+
}
402416
Model flattenedPom = createFlattenedPom( originalPomFile );
403417
String headerComment = extractHeaderComment( originalPomFile );
404418

405419
File flattenedPomFile = getFlattenedPomFile();
406-
writePom( flattenedPom, flattenedPomFile, headerComment );
420+
writePom( flattenedPom, flattenedPomFile, headerComment , commentsOfOriginalPomFile);
407421

408422
if ( isUpdatePomFile() )
409423
{
410424
this.project.setPomFile( flattenedPomFile );
411425
}
412426
}
413427

414-
/**
428+
429+
430+
/**
415431
* This method extracts the XML header comment if available.
416432
*
417433
* @param xmlFile is the XML {@link File} to parse.
@@ -447,7 +463,7 @@ protected String extractHeaderComment( File xmlFile )
447463
* before root tag). May be <code>null</code> if not present and to be omitted in target POM.
448464
* @throws MojoExecutionException if the operation failed (e.g. due to an {@link IOException}).
449465
*/
450-
protected void writePom( Model pom, File pomFile, String headerComment )
466+
protected void writePom( Model pom, File pomFile, String headerComment, KeepCommentsInPom anOriginalCommentsPath )
451467
throws MojoExecutionException
452468
{
453469

@@ -485,10 +501,16 @@ protected void writePom( Model pom, File pomFile, String headerComment )
485501
getLog().warn( "POM XML post-processing failed: no project tag found!" );
486502
}
487503
}
488-
writeStringToFile( buffer.toString(), pomFile, pom.getModelEncoding() );
504+
String xmlString;
505+
if (anOriginalCommentsPath == null) {
506+
xmlString = buffer.toString();
507+
} else {
508+
xmlString = anOriginalCommentsPath.restoreOriginalComments(buffer.toString(), pom.getModelEncoding());
509+
}
510+
writeStringToFile( xmlString, pomFile, pom.getModelEncoding() );
489511
}
490512

491-
/**
513+
/**
492514
* Writes the given <code>data</code> to the given <code>file</code> using the specified <code>encoding</code>.
493515
*
494516
* @param data is the {@link String} to write.
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
package org.codehaus.mojo.flatten;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.util.ArrayList;
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.function.BiConsumer;
12+
13+
import javax.xml.parsers.DocumentBuilder;
14+
import javax.xml.parsers.DocumentBuilderFactory;
15+
import javax.xml.parsers.ParserConfigurationException;
16+
17+
import org.apache.maven.plugin.MojoExecutionException;
18+
import org.apache.maven.plugin.logging.Log;
19+
import org.w3c.dom.Document;
20+
import org.w3c.dom.Node;
21+
import org.w3c.dom.NodeList;
22+
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
23+
import org.w3c.dom.ls.DOMImplementationLS;
24+
import org.w3c.dom.ls.LSOutput;
25+
import org.w3c.dom.ls.LSSerializer;
26+
import org.xml.sax.SAXException;
27+
28+
/**
29+
* Helper class to keep the comments how they have been in the original pom.xml While reading with
30+
* {@link org.apache.maven.model.io.xpp3.MavenXpp3Writer} the comments are not placed into the
31+
* {@link org.apache.maven.model.Model} and so {@link org.apache.maven.model.io.xpp3.MavenXpp3Writer} is not able to
32+
* re-write those comments.
33+
*
34+
* Workaround (maybe until core is fixed) is to remember all the comments and restore them after MavenXpp3Writer has
35+
* created the new flattened pom.xml.
36+
*
37+
* Current restriction on non-unique child nodes is that this class finds the node back due to the position in the file,
38+
* that may lead to mis-re-added comments e.g. on multiple added dependencies (but for e.g. resolveCiFriendliesOnly the
39+
* nodes keep stable)
40+
*
41+
*/
42+
class KeepCommentsInPom
43+
{
44+
45+
/**
46+
* Create an instance with collected current comments of the passed pom.xml file.
47+
*/
48+
static KeepCommentsInPom create(Log aLog, File aOriginalPomFile) throws MojoExecutionException
49+
{
50+
KeepCommentsInPom tempKeepCommentsInPom = new KeepCommentsInPom();
51+
tempKeepCommentsInPom.setLog(aLog);
52+
tempKeepCommentsInPom.loadComments(aOriginalPomFile);
53+
return tempKeepCommentsInPom;
54+
}
55+
56+
private Log log;
57+
58+
/**
59+
* The unique path list for an original node (the comments are stored via the referenced previousSibling)
60+
*/
61+
private Map<String, Node> commentsPaths;
62+
63+
/**
64+
*
65+
*/
66+
KeepCommentsInPom()
67+
{
68+
super();
69+
}
70+
71+
/**
72+
* load all current comments and text fragments from xml file
73+
*
74+
* @param anOriginalPomFile the pom.xml
75+
*/
76+
private void loadComments(File anOriginalPomFile) throws MojoExecutionException
77+
{
78+
commentsPaths = new HashMap<>();
79+
DocumentBuilderFactory tempDBF = DocumentBuilderFactory.newInstance();
80+
DocumentBuilder tempDB;
81+
try
82+
{
83+
tempDB = tempDBF.newDocumentBuilder();
84+
Document tempPom = tempDB.parse(anOriginalPomFile);
85+
Node tempNode = tempPom.getDocumentElement();
86+
walkOverNodes(tempNode, ".", (node, nodePath) ->
87+
{
88+
// collectNodesByPathNames
89+
commentsPaths.put(nodePath, node);
90+
});
91+
} catch (ParserConfigurationException | SAXException | IOException e)
92+
{
93+
throw new MojoExecutionException("Cannot load comments from " + anOriginalPomFile, e);
94+
}
95+
}
96+
97+
/**
98+
* Walk over the pom hierarchy of the Document.
99+
*
100+
* @param Node the current Node
101+
* @param String the unique path in the parent
102+
* @param aConsumer Function to be called with the toBeCollected/found node.
103+
*/
104+
private void walkOverNodes(Node aNode, String aParentPath, BiConsumer<Node, String> aConsumer)
105+
{
106+
String tempNodeName = aNode.getNodeName();
107+
if (log.isDebugEnabled())
108+
{
109+
log.debug("walkOverNodes: aParentPath=" + aParentPath + " tempNodeName=" + tempNodeName);
110+
}
111+
String tempNodePath = aParentPath + "\t" + tempNodeName;
112+
aConsumer.accept(aNode, tempNodePath);
113+
NodeList tempChilds = aNode.getChildNodes();
114+
// Copy the childs as aConsumer may change the node sequence (add a comment)
115+
List<Node> tempCopiedChilds = new ArrayList<>();
116+
Map<String, Integer> tempChildWithSameName = new HashMap<>();
117+
for (int i = 0; i < tempChilds.getLength(); i++)
118+
{
119+
Node tempItem = tempChilds.item(i);
120+
if (tempItem.getNodeType() != Node.TEXT_NODE && tempItem.getNodeType() != Node.COMMENT_NODE)
121+
{
122+
// Take real nodes to find them back by number
123+
String tempChildNodeName = tempItem.getNodeName();
124+
Integer tempChildWithSameNameCount = tempChildWithSameName.get(tempChildNodeName);
125+
if (tempChildWithSameNameCount == null)
126+
{
127+
tempChildWithSameNameCount = 1;
128+
} else
129+
{
130+
tempChildWithSameNameCount += 1;
131+
}
132+
tempChildWithSameName.put(tempChildNodeName, tempChildWithSameNameCount);
133+
tempCopiedChilds.add(tempItem);
134+
}
135+
}
136+
Map<String, Integer> tempChildWithSameNameCounters = new HashMap<>();
137+
for (int i = 0; i < tempCopiedChilds.size(); i++)
138+
{
139+
Node tempCopiedChild = tempCopiedChilds.get(i);
140+
String tempChildNodeName = tempCopiedChild.getNodeName();
141+
if (tempChildWithSameName.get(tempChildNodeName) > 1)
142+
{
143+
Integer tempChildWithSameNameCounter = tempChildWithSameNameCounters.get(tempChildNodeName);
144+
if (tempChildWithSameNameCounter == null)
145+
{
146+
tempChildWithSameNameCounter = 1;
147+
} else
148+
{
149+
tempChildWithSameNameCounter += 1;
150+
}
151+
tempChildWithSameNameCounters.put(tempChildNodeName, tempChildWithSameNameCounter);
152+
// add a counter to find back the correct node.
153+
walkOverNodes(tempCopiedChild, tempNodePath + "\t" + tempChildWithSameNameCounter, aConsumer);
154+
} else
155+
{
156+
// unique child names
157+
walkOverNodes(tempCopiedChild, tempNodePath, aConsumer);
158+
}
159+
}
160+
}
161+
162+
/**
163+
* @param String the XML written by {@link org.apache.maven.model.io.xpp3.MavenXpp3Writer}
164+
*/
165+
public String restoreOriginalComments(String anXml, String aModelEncoding) throws MojoExecutionException
166+
{
167+
DocumentBuilderFactory tempDBF = DocumentBuilderFactory.newInstance();
168+
DocumentBuilder tempDB;
169+
try
170+
{
171+
tempDB = tempDBF.newDocumentBuilder();
172+
String tempEncoding = aModelEncoding == null ? "UTF-8" : aModelEncoding; // default encoding UTF-8 when
173+
// nothing in pom model.
174+
Document tempPom = tempDB.parse(new ByteArrayInputStream(anXml.getBytes(tempEncoding)));
175+
Node tempNode = tempPom.getDocumentElement();
176+
walkOverNodes(tempNode, ".", (newNode, nodePath) ->
177+
{
178+
Node tempOriginalNode = commentsPaths.get(nodePath);
179+
if (tempOriginalNode != null)
180+
{
181+
String tempOriginalNodeName = tempOriginalNode.getNodeName();
182+
if (tempOriginalNodeName.equals(newNode.getNodeName()))
183+
{
184+
// found matching node
185+
Node tempRefChild = newNode;
186+
Node tempPotentialCommentOrText = tempOriginalNode.getPreviousSibling();
187+
while (tempPotentialCommentOrText != null
188+
&& tempPotentialCommentOrText.getNodeType() == Node.TEXT_NODE)
189+
{
190+
// skip text in the original xml node
191+
tempPotentialCommentOrText = tempPotentialCommentOrText.getPreviousSibling();
192+
}
193+
while (tempPotentialCommentOrText != null
194+
&& tempPotentialCommentOrText.getNodeType() == Node.COMMENT_NODE)
195+
{
196+
// copy the node to be able to call previoussibling for next element
197+
Node tempRefPrevious = tempRefChild.getPreviousSibling();
198+
String tempWhitespaceTextBeforeRefNode = null;
199+
if (tempRefPrevious != null && tempRefPrevious.getNodeType() == Node.TEXT_NODE)
200+
{
201+
tempWhitespaceTextBeforeRefNode = tempRefPrevious.getNodeValue();
202+
}
203+
Node tempNewComment;
204+
tempNewComment = tempPom.createComment(tempPotentialCommentOrText.getNodeValue());
205+
tempRefChild.getParentNode().insertBefore(tempNewComment, tempRefChild);
206+
// copy the whitespaces between comment and refNode
207+
if (tempWhitespaceTextBeforeRefNode != null)
208+
{
209+
tempRefChild.getParentNode().insertBefore(
210+
tempPom.createTextNode(tempWhitespaceTextBeforeRefNode), tempRefChild);
211+
}
212+
213+
tempRefChild = tempNewComment;
214+
215+
tempPotentialCommentOrText = tempPotentialCommentOrText.getPreviousSibling();
216+
while (tempPotentialCommentOrText != null
217+
&& tempPotentialCommentOrText.getNodeType() == Node.TEXT_NODE)
218+
{
219+
// skip text in the original xml node
220+
tempPotentialCommentOrText = tempPotentialCommentOrText.getPreviousSibling();
221+
}
222+
}
223+
}
224+
}
225+
});
226+
return writeDocumentToString(tempPom);
227+
} catch (ParserConfigurationException | SAXException | IOException | ClassNotFoundException
228+
| InstantiationException | IllegalAccessException | ClassCastException e)
229+
{
230+
throw new MojoExecutionException("Cannot add comments", e);
231+
}
232+
}
233+
234+
/**
235+
* Use an LSSerializer to keep whitespaces added by MavenXpp3Writer
236+
*
237+
* @param Document the pom to write to String.
238+
*/
239+
private String writeDocumentToString(Document aPom)
240+
throws ClassNotFoundException, InstantiationException, IllegalAccessException
241+
{
242+
DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
243+
DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
244+
LSOutput output = impl.createLSOutput();
245+
output.setEncoding("UTF-8");
246+
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
247+
output.setByteStream(outStream);
248+
LSSerializer writer = impl.createLSSerializer();
249+
writer.write(aPom, output);
250+
return new String(outStream.toByteArray());
251+
}
252+
253+
/**
254+
* @see #log
255+
*/
256+
public Log getLog()
257+
{
258+
return log;
259+
}
260+
261+
/**
262+
* @see #log
263+
*/
264+
public void setLog(Log aLog)
265+
{
266+
log = aLog;
267+
}
268+
269+
}

0 commit comments

Comments
 (0)