Skip to content

Fix [MDEP-931] Replace PrintWriter with Writer in AbstractSerializing Visitor and subclasses #530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/
package org.apache.maven.plugins.dependency.tree;

import java.io.PrintWriter;
import java.io.Writer;

/**
Expand All @@ -31,7 +30,7 @@ public abstract class AbstractSerializingVisitor {
/**
* The writer to serialize to.
*/
protected final PrintWriter writer;
protected final Writer writer;

/**
* Constructor.
Expand All @@ -42,10 +41,6 @@ public abstract class AbstractSerializingVisitor {
* @param writer the writer to serialize to.
*/
public AbstractSerializingVisitor(Writer writer) {
if (writer instanceof PrintWriter) {
this.writer = (PrintWriter) writer;
} else {
this.writer = new PrintWriter(writer, true);
}
this.writer = writer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
package org.apache.maven.plugins.dependency.tree;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.util.List;

Expand Down Expand Up @@ -47,29 +50,45 @@ public DOTDependencyNodeVisitor(Writer writer) {
*/
@Override
public boolean visit(DependencyNode node) {
if (node.getParent() == null || node.getParent() == node) {
writer.write("digraph \"" + node.toNodeString() + "\" { " + System.lineSeparator());
}
try {
StringWriter stringWriter = new StringWriter();
if (node.getParent() == null || node.getParent() == node) {
writer.write("digraph \"" + node.toNodeString() + "\" { " + System.lineSeparator());
}

// Generate "currentNode -> Child" lines
// Generate "currentNode -> Child" lines

List<DependencyNode> children = node.getChildren();
List<DependencyNode> children = node.getChildren();
for (DependencyNode child : children) {
stringWriter.write("\t\"" + node.toNodeString() + "\" -> \"" + child.toNodeString() + "\" ;\n");
}

for (DependencyNode child : children) {
writer.println("\t\"" + node.toNodeString() + "\" -> \"" + child.toNodeString() + "\" ; ");
// Write the accumulated output to the provided writer
writer.write(stringWriter.toString());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be happening in the visit method. The visitor should be building the report in memory that is only later written to a stream. Alternately the API should change so that visit can throw an IOException.

writer.flush();
return true;
} catch (IOException e) {
throw new UncheckedIOException("Failed to write to DOT output", e);
Copy link
Contributor

@elharo elharo May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably better than what we had before, but really we need a complete rethink of this API that separates I/O from string buliding and lets it throw exceptions

}

return true;
}

/**
* {@inheritDoc}
*/
@Override
public boolean endVisit(DependencyNode node) {
if (node.getParent() == null || node.getParent() == node) {
writer.write(" } ");
try {
StringWriter stringWriter = new StringWriter();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need the stringwriter here. Just use the writer directly

if (node.getParent() == null || node.getParent() == node) {
stringWriter.write("}\n");
}

// Write the accumulated output to the provided writer
writer.write(stringWriter.toString());
writer.flush();
return true;
} catch (IOException e) {
throw new UncheckedIOException("Failed to write to DOT output", e);
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
package org.apache.maven.plugins.dependency.tree;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.io.Writer;

import org.apache.maven.shared.dependency.graph.DependencyNode;
Expand Down Expand Up @@ -63,37 +66,55 @@ public GraphmlDependencyNodeVisitor(Writer writer) {
* {@inheritDoc}
*/
@Override
public boolean endVisit(DependencyNode node) {
if (node.getParent() == null || node.getParent() == node) {
writer.write(GRAPHML_FOOTER);
} else {
DependencyNode p = node.getParent();
writer.print("<edge source=\"" + generateId(p) + "\" target=\"" + generateId(node) + "\">");
if (node.getArtifact().getScope() != null) {
// add Edge label
writer.print("<data key=\"d1\"><y:PolyLineEdge><y:EdgeLabel>"
+ node.getArtifact().getScope() + "</y:EdgeLabel></y:PolyLineEdge></data>");
public boolean visit(DependencyNode node) {
try {
StringWriter stringWriter = new StringWriter();
if (node.getParent() == null || node.getParent() == node) {
stringWriter.write(GRAPHML_HEADER);
}
writer.println("</edge>");
// write node
stringWriter.write("<node id=\"" + generateId(node) + "\">");
// add node label
stringWriter.write("<data key=\"d0\"><y:ShapeNode><y:NodeLabel>" + node.toNodeString()
+ "</y:NodeLabel></y:ShapeNode></data>");
stringWriter.write("</node>\n");

// Write the accumulated output to the provided writer
writer.write(stringWriter.toString());
writer.flush();
return true;
} catch (IOException e) {
throw new UncheckedIOException("Failed to write GraphML node", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm starting to see how this should work. Either drop all the stringwiters and write straight onto the writer, or use stringwriters to build up a report that is written after the visitor is finished.

}
return true;
}

/**
* {@inheritDoc}
*/
@Override
public boolean visit(DependencyNode node) {
if (node.getParent() == null || node.getParent() == node) {
writer.write(GRAPHML_HEADER);
public boolean endVisit(DependencyNode node) {
try {
StringWriter stringWriter = new StringWriter();
if (node.getParent() == null || node.getParent() == node) {
stringWriter.write(GRAPHML_FOOTER);
} else {
DependencyNode p = node.getParent();
stringWriter.write("<edge source=\"" + generateId(p) + "\" target=\"" + generateId(node) + "\">");
if (node.getArtifact().getScope() != null) {
// add Edge label
stringWriter.write("<data key=\"d1\"><y:PolyLineEdge><y:EdgeLabel>"
+ node.getArtifact().getScope() + "</y:EdgeLabel></y:PolyLineEdge></data>");
}
stringWriter.write("</edge>\n");
}

// Write the accumulated output to the provided writer
writer.write(stringWriter.toString());
writer.flush();
return true;
} catch (IOException e) {
throw new UncheckedIOException("Failed to write GraphML edge or footer", e);
}
// write node
writer.print("<node id=\"" + generateId(node) + "\">");
// add node label
writer.print("<data key=\"d0\"><y:ShapeNode><y:NodeLabel>" + node.toNodeString()
+ "</y:NodeLabel></y:ShapeNode></data>");
writer.println("</node>");
return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.maven.plugins.dependency.tree;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -56,14 +58,20 @@ public boolean visit(DependencyNode node) {
* @param node the node to write
*/
private void writeRootNode(DependencyNode node) {
Set<DependencyNode> visited = new HashSet<>();
int indent = 2;
StringBuilder sb = new StringBuilder();
sb.append("{").append("\n");
writeNode(indent, node, sb, visited);
sb.append("}").append("\n");
writer.write(sb.toString());
try {
StringBuilder sb = new StringBuilder();
Set<DependencyNode> visited = new HashSet<>();
int indent = 2;
sb.append("{").append("\n");
writeNode(indent, node, sb, visited);
sb.append("}").append("\n");
writer.write(sb.toString());
writer.flush();
} catch (IOException e) {
throw new UncheckedIOException("Failed to write JSON output", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed; this method can and should throw the IOException directly since it's private

}
}

/**
* Appends the node and its children to the string builder.
*
Expand All @@ -83,6 +91,7 @@ private void writeNode(int indent, DependencyNode node, StringBuilder sb, Set<De
writeChildren(indent, node, sb, visited);
}
}

/**
* Writes the children of the node to the string builder. And each children of each node will be written recursively.
*
Expand Down Expand Up @@ -110,8 +119,16 @@ private void writeChildren(int indent, DependencyNode node, StringBuilder sb, Se

@Override
public boolean endVisit(DependencyNode node) {
return true;
try {
if (node.getParent() == null || node.getParent() == node) {
writer.flush();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly here and only here content should be written to the stream; I'm not sure

}
return true;
} catch (IOException e) {
throw new UncheckedIOException("Failed to flush JSON output", e);
}
}

/**
* Appends the artifact values to the string builder.
*
Expand All @@ -133,6 +150,7 @@ private void appendNodeValues(StringBuilder sb, int indent, Artifact artifact, b
appendKeyWithoutComma(sb, indent, "optional", String.valueOf(artifact.isOptional()));
}
}

/**
* Appends a key value pair to the string builder.
*
Expand All @@ -158,6 +176,7 @@ private void appendKeyValue(StringBuilder sb, int indent, String key, String val
.append(",")
.append("\n");
}

/**
* Appends a key value pair to the string builder without a comma at the end. This is used for the last children of a node.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
package org.apache.maven.plugins.dependency.tree;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -35,7 +38,7 @@
public class TGFDependencyNodeVisitor extends AbstractSerializingVisitor implements DependencyNodeVisitor {

/**
* Utiity class to write an Edge.
* Utility class to write an Edge.
*
* @author <a href="mailto:[email protected]">Jerome Creignou</a>
*/
Expand Down Expand Up @@ -101,31 +104,50 @@ public TGFDependencyNodeVisitor(Writer writer) {
* {@inheritDoc}
*/
@Override
public boolean endVisit(DependencyNode node) {
if (node.getParent() == null || node.getParent() == node) {
// dump edges on last node endVisit
writer.println("#");
for (EdgeAppender edge : edges) {
writer.println(edge.toString());
}
} else {
DependencyNode p = node.getParent();
// using scope as edge label.
edges.add(new EdgeAppender(p, node, node.getArtifact().getScope()));
public boolean visit(DependencyNode node) {
try {
StringWriter stringWriter = new StringWriter();
// write node
stringWriter.write(generateId(node));
stringWriter.write(" ");
stringWriter.write(node.toNodeString());
stringWriter.write("\n");

// Write the accumulated output to the provided writer
writer.write(stringWriter.toString());
writer.flush();
return true;
} catch (IOException e) {
throw new UncheckedIOException("Failed to write TGF node", e);
}
return true;
}

/**
* {@inheritDoc}
*/
@Override
public boolean visit(DependencyNode node) {
// write node
writer.write(generateId(node));
writer.write(" ");
writer.println(node.toNodeString());
return true;
public boolean endVisit(DependencyNode node) {
try {
StringWriter stringWriter = new StringWriter();
if (node.getParent() == null || node.getParent() == node) {
// dump edges on last node endVisit
stringWriter.write("#\n");
for (EdgeAppender edge : edges) {
stringWriter.write(edge.toString());
}

// Write the accumulated output to the provided writer
writer.write(stringWriter.toString());
writer.flush();
} else {
DependencyNode parent = node.getParent();
// using scope as edge label.
edges.add(new EdgeAppender(parent, node, node.getArtifact().getScope()));
}
return true;
} catch (IOException e) {
throw new UncheckedIOException("Failed to write TGF edges or footer", e);
}
}

/**
Expand Down