Skip to content

Commit d600227

Browse files
authored
Make jdom IO reusable (#170)
Make jdom IO reusable and some more refactoring.
1 parent 9bad973 commit d600227

File tree

4 files changed

+169
-108
lines changed

4 files changed

+169
-108
lines changed

shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/PomTransformerSink.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ public static PomTransformerSink transform(
5555

5656
private static final String BLANK_POM = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" //
5757
+ "<project xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" //
58-
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" //
58+
+ " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" //
5959
+ " <modelVersion>4.0.0</modelVersion>\n" //
60+
+ "\n" //
6061
+ " <groupId>org.acme</groupId>\n" //
6162
+ " <artifactId>pom</artifactId>\n" //
6263
+ " <version>1.0-SNAPSHOT</version>\n" //

shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/jdom/JDomCleanupHelper.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@
4545
*/
4646
public class JDomCleanupHelper {
4747

48+
/**
49+
* Generic cleanup useful after removal of things.
50+
*/
51+
public static void cleanup(Element rootElement) {
52+
// Remove empty elements
53+
for (String cleanUpEmptyElement : List.of(
54+
JDomCfg.POM_ELEMENT_MODULES,
55+
JDomCfg.POM_ELEMENT_PROPERTIES,
56+
JDomCfg.POM_ELEMENT_PLUGINS,
57+
JDomCfg.POM_ELEMENT_PLUGIN_MANAGEMENT,
58+
JDomCfg.POM_ELEMENT_DEPENDENCIES,
59+
JDomCfg.POM_ELEMENT_DEPENDENCY_MANAGEMENT)) {
60+
JDomCleanupHelper.cleanupEmptyElements(rootElement, cleanUpEmptyElement);
61+
}
62+
// Remove empty (i.e. with no elements) profile and profiles tag
63+
JDomCleanupHelper.cleanupEmptyProfiles(rootElement, List.of(JDomCfg.POM_ELEMENT_PROJECT));
64+
}
65+
4866
/**
4967
* Remove all empty profiles and profile tags.<br>
5068
* Empty is defined as
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2023-2024 Maveniverse Org.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v20.html
7+
*/
8+
package eu.maveniverse.maven.toolbox.shared.internal.jdom;
9+
10+
import static java.util.Objects.requireNonNull;
11+
12+
import java.io.ByteArrayOutputStream;
13+
import java.io.Closeable;
14+
import java.io.IOException;
15+
import java.io.OutputStream;
16+
import java.io.StringReader;
17+
import java.nio.charset.StandardCharsets;
18+
import java.nio.file.Files;
19+
import java.nio.file.Path;
20+
import java.util.Iterator;
21+
import java.util.Objects;
22+
import org.jdom2.CDATA;
23+
import org.jdom2.Comment;
24+
import org.jdom2.Document;
25+
import org.jdom2.JDOMException;
26+
import org.jdom2.filter.ContentFilter;
27+
import org.jdom2.input.SAXBuilder;
28+
import org.jdom2.located.LocatedJDOMFactory;
29+
import org.jdom2.output.Format;
30+
import org.jdom2.output.XMLOutputter;
31+
32+
/**
33+
* Class that parses and writes possibly manipulated JDOM to file.
34+
*/
35+
public final class JDomDocumentIO implements Closeable {
36+
private final Path xmlFile;
37+
private final String lineSeparator;
38+
private final String body;
39+
private final String head;
40+
private final String tail;
41+
private final Document document;
42+
43+
public JDomDocumentIO(Path xmlFile) throws IOException {
44+
this(xmlFile, System.lineSeparator());
45+
}
46+
47+
public JDomDocumentIO(Path xmlFile, String lineSeparator) throws IOException {
48+
this.xmlFile = requireNonNull(xmlFile, "xmlFile");
49+
this.lineSeparator = requireNonNull(lineSeparator, "lineSeparator");
50+
51+
this.body = normalizeLineEndings(Files.readString(xmlFile, StandardCharsets.UTF_8), lineSeparator);
52+
SAXBuilder builder = new SAXBuilder();
53+
builder.setJDOMFactory(new LocatedJDOMFactory());
54+
try {
55+
this.document = builder.build(new StringReader(body));
56+
} catch (JDOMException e) {
57+
throw new IOException(e);
58+
}
59+
normaliseLineEndings(document, lineSeparator);
60+
61+
int headIndex = body.indexOf("<" + document.getRootElement().getName());
62+
if (headIndex >= 0) {
63+
this.head = body.substring(0, headIndex);
64+
} else {
65+
this.head = null;
66+
}
67+
String lastTag = "</" + document.getRootElement().getName() + ">";
68+
int tailIndex = body.lastIndexOf(lastTag);
69+
if (tailIndex >= 0) {
70+
this.tail = body.substring(tailIndex + lastTag.length());
71+
} else {
72+
this.tail = null;
73+
}
74+
}
75+
76+
public Document getDocument() {
77+
return document;
78+
}
79+
80+
@Override
81+
public void close() throws IOException {
82+
Format format = Format.getRawFormat();
83+
format.setLineSeparator(lineSeparator);
84+
XMLOutputter out = new XMLOutputter(format);
85+
ByteArrayOutputStream output = new ByteArrayOutputStream();
86+
try (OutputStream outputStream = output) {
87+
if (head != null) {
88+
outputStream.write(head.getBytes(StandardCharsets.UTF_8));
89+
}
90+
out.output(document.getRootElement(), outputStream);
91+
if (tail != null) {
92+
outputStream.write(tail.getBytes(StandardCharsets.UTF_8));
93+
}
94+
}
95+
String newBody = output.toString(StandardCharsets.UTF_8);
96+
if (!Objects.equals(body, newBody)) {
97+
Files.writeString(xmlFile, newBody, StandardCharsets.UTF_8);
98+
}
99+
}
100+
101+
private static void normaliseLineEndings(Document document, String lineSeparator) {
102+
for (Iterator<?> i = document.getDescendants(new ContentFilter(ContentFilter.COMMENT)); i.hasNext(); ) {
103+
Comment c = (Comment) i.next();
104+
c.setText(normalizeLineEndings(c.getText(), lineSeparator));
105+
}
106+
for (Iterator<?> i = document.getDescendants(new ContentFilter(ContentFilter.CDATA)); i.hasNext(); ) {
107+
CDATA c = (CDATA) i.next();
108+
c.setText(normalizeLineEndings(c.getText(), lineSeparator));
109+
}
110+
}
111+
112+
private static String normalizeLineEndings(String text, String separator) {
113+
String norm = text;
114+
if (text != null) {
115+
norm = text.replaceAll("(\r\n)|(\n)|(\r)", separator);
116+
}
117+
return norm;
118+
}
119+
}

shared/src/main/java/eu/maveniverse/maven/toolbox/shared/internal/jdom/JDomPomTransformer.java

Lines changed: 30 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,15 @@
1010
import static java.util.Objects.requireNonNull;
1111

1212
import java.io.IOException;
13-
import java.io.OutputStream;
14-
import java.io.StringReader;
15-
import java.nio.charset.StandardCharsets;
16-
import java.nio.file.Files;
1713
import java.nio.file.Path;
1814
import java.util.ArrayList;
19-
import java.util.Iterator;
2015
import java.util.List;
2116
import java.util.Objects;
2217
import java.util.function.Consumer;
2318
import java.util.function.Function;
2419
import org.eclipse.aether.artifact.Artifact;
25-
import org.jdom2.CDATA;
26-
import org.jdom2.Comment;
2720
import org.jdom2.Document;
2821
import org.jdom2.Element;
29-
import org.jdom2.JDOMException;
30-
import org.jdom2.filter.ContentFilter;
31-
import org.jdom2.input.SAXBuilder;
32-
import org.jdom2.located.LocatedJDOMFactory;
33-
import org.jdom2.output.Format;
34-
import org.jdom2.output.XMLOutputter;
3522

3623
/**
3724
* Construction to accept collection of artifacts, and applies it to some POM based on provided transformations.
@@ -52,19 +39,7 @@ public interface TransformationContext {
5239
* Removes empty remnant tags, like {@code <plugins />}.
5340
*/
5441
private static final Consumer<TransformationContext> removeEmptyElements = ctx -> {
55-
// Remove empty elements
56-
for (String cleanUpEmptyElement : List.of(
57-
JDomCfg.POM_ELEMENT_MODULES,
58-
JDomCfg.POM_ELEMENT_PROPERTIES,
59-
JDomCfg.POM_ELEMENT_PLUGINS,
60-
JDomCfg.POM_ELEMENT_PLUGIN_MANAGEMENT,
61-
JDomCfg.POM_ELEMENT_DEPENDENCIES,
62-
JDomCfg.POM_ELEMENT_DEPENDENCY_MANAGEMENT)) {
63-
JDomCleanupHelper.cleanupEmptyElements(ctx.getDocument().getRootElement(), cleanUpEmptyElement);
64-
}
65-
// Remove empty (i.e. with no elements) profile and profiles tag
66-
JDomCleanupHelper.cleanupEmptyProfiles(
67-
ctx.getDocument().getRootElement(), List.of(JDomCfg.POM_ELEMENT_PROJECT));
42+
JDomCleanupHelper.cleanup(ctx.getDocument().getRootElement());
6843
};
6944

7045
/**
@@ -443,92 +418,40 @@ public void apply(Path pom, List<Consumer<TransformationContext>> transformation
443418
requireNonNull(pom, "pom");
444419
requireNonNull(transformations, "transformations");
445420
if (!transformations.isEmpty()) {
446-
String head = null;
447-
String body;
448-
String tail = null;
449-
450-
body = Files.readString(pom, StandardCharsets.UTF_8);
451-
body = normalizeLineEndings(body, System.lineSeparator());
452-
453-
SAXBuilder builder = new SAXBuilder();
454-
builder.setJDOMFactory(new LocatedJDOMFactory());
455-
Document document;
456-
try {
457-
document = builder.build(new StringReader(body));
458-
} catch (JDOMException e) {
459-
throw new IOException(e);
460-
}
461-
normaliseLineEndings(document, System.lineSeparator());
462-
463-
int headIndex = body.indexOf("<" + document.getRootElement().getName());
464-
if (headIndex >= 0) {
465-
head = body.substring(0, headIndex);
466-
}
467-
String lastTag = "</" + document.getRootElement().getName() + ">";
468-
int tailIndex = body.lastIndexOf(lastTag);
469-
if (tailIndex >= 0) {
470-
tail = body.substring(tailIndex + lastTag.length());
471-
}
472-
473-
ArrayList<Consumer<TransformationContext>> postProcessors = new ArrayList<>();
474-
TransformationContext context = new TransformationContext() {
475-
@Override
476-
public boolean pomHasParent() {
477-
return document.getRootElement()
478-
.getChild(
479-
"parent", document.getRootElement().getNamespace())
480-
!= null;
481-
}
482-
483-
@Override
484-
public Document getDocument() {
485-
return document;
486-
}
421+
try (JDomDocumentIO domDocumentIO = new JDomDocumentIO(pom)) {
422+
ArrayList<Consumer<TransformationContext>> postProcessors = new ArrayList<>();
423+
TransformationContext context = new TransformationContext() {
424+
@Override
425+
public boolean pomHasParent() {
426+
return domDocumentIO
427+
.getDocument()
428+
.getRootElement()
429+
.getChild(
430+
"parent",
431+
domDocumentIO
432+
.getDocument()
433+
.getRootElement()
434+
.getNamespace())
435+
!= null;
436+
}
487437

488-
@Override
489-
public void registerPostTransformation(Consumer<TransformationContext> transformation) {
490-
postProcessors.add(transformation);
491-
}
492-
};
493-
for (Consumer<TransformationContext> transformation : transformations) {
494-
transformation.accept(context);
495-
}
496-
for (Consumer<TransformationContext> transformation : postProcessors) {
497-
transformation.accept(context);
498-
}
438+
@Override
439+
public Document getDocument() {
440+
return domDocumentIO.getDocument();
441+
}
499442

500-
Format format = Format.getRawFormat();
501-
format.setLineSeparator(System.lineSeparator());
502-
XMLOutputter out = new XMLOutputter(format);
503-
try (OutputStream outputStream = Files.newOutputStream(pom)) {
504-
if (head != null) {
505-
outputStream.write(head.getBytes(StandardCharsets.UTF_8));
443+
@Override
444+
public void registerPostTransformation(Consumer<TransformationContext> transformation) {
445+
postProcessors.add(transformation);
446+
}
447+
};
448+
for (Consumer<TransformationContext> transformation : transformations) {
449+
transformation.accept(context);
506450
}
507-
out.output(document.getRootElement(), outputStream);
508-
if (tail != null) {
509-
outputStream.write(tail.getBytes(StandardCharsets.UTF_8));
451+
for (Consumer<TransformationContext> transformation : postProcessors) {
452+
transformation.accept(context);
510453
}
511-
outputStream.flush();
512454
}
513455
}
514456
}
515-
516-
private static void normaliseLineEndings(Document document, String lineSeparator) {
517-
for (Iterator<?> i = document.getDescendants(new ContentFilter(ContentFilter.COMMENT)); i.hasNext(); ) {
518-
Comment c = (Comment) i.next();
519-
c.setText(normalizeLineEndings(c.getText(), lineSeparator));
520-
}
521-
for (Iterator<?> i = document.getDescendants(new ContentFilter(ContentFilter.CDATA)); i.hasNext(); ) {
522-
CDATA c = (CDATA) i.next();
523-
c.setText(normalizeLineEndings(c.getText(), lineSeparator));
524-
}
525-
}
526-
527-
private static String normalizeLineEndings(String text, String separator) {
528-
String norm = text;
529-
if (text != null) {
530-
norm = text.replaceAll("(\r\n)|(\n)|(\r)", separator);
531-
}
532-
return norm;
533-
}
534457
}

0 commit comments

Comments
 (0)