Skip to content

Commit

Permalink
feat(target): Simplify xml document merging
Browse files Browse the repository at this point in the history
  • Loading branch information
olivergondza committed Jul 29, 2024
1 parent 029a3f1 commit 071ed71
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 96 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down
13 changes: 2 additions & 11 deletions src/main/java/com/github/olivergondza/saxeed/Saxeed.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@
public class Saxeed {

private SAXParser saxParser;
private XMLOutputFactory xmlOutputFactory;
private InputSource input;
private final Map<TransformationBuilder, Target> transformations = new LinkedHashMap<>();

public Saxeed() {
setXmlOutputFactory(null); // Initialize with defaults

}

public Saxeed setSaxParser(SAXParser saxParser) {
Expand All @@ -44,14 +43,6 @@ public Saxeed setSaxParser(SAXParser saxParser) {
return this;
}

public Saxeed setXmlOutputFactory(XMLOutputFactory xmlOutputFactory) {
this.xmlOutputFactory = xmlOutputFactory != null
? xmlOutputFactory
: XMLOutputFactory.newInstance()
;
return this;
}

public Saxeed setInput(Path path) {
input = new InputSource(path.toFile().toURI().toASCIIString());
return this;
Expand Down Expand Up @@ -167,7 +158,7 @@ private MultiplexingHandler getSaxHandler() {
Target target = trans.getValue();
TransformationBuilder builder = trans.getKey();

return target.getHandler(builder, xmlOutputFactory);
return builder.build(this, target);
}).collect(Collectors.toList());
return new MultiplexingHandler(handlers);
}
Expand Down
72 changes: 53 additions & 19 deletions src/main/java/com/github/olivergondza/saxeed/Target.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.olivergondza.saxeed;

import com.github.olivergondza.saxeed.ex.FailedWriting;
import com.github.olivergondza.saxeed.internal.TransformationHandler;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
Expand All @@ -16,22 +16,29 @@
/**
* Target to write the resulting content into.
*/
public interface Target {
public abstract class Target implements AutoCloseable {

private AutoCloseable close = null;

/**
* Identify the target for the ease of debugging.
*
* Mostly used in error messages to identify the file/stream/etc.
*/
String getName();
public abstract String getName();

TransformationHandler getHandler(TransformationBuilder builder, XMLOutputFactory xmlOutputFactory);
/**
* Create new XMLStreamWriter for the transformation.
*
* If it is desirable to close any resource allocated, register it by {@link #registerClosable(AutoCloseable)}.
*/
public abstract XMLStreamWriter getWriter(Saxeed saxeed);

default XMLStreamWriter create(XMLOutputFactory xmlOutputFactory, OutputStream os) {
public static XMLStreamWriter createXmlStreamWriter(OutputStream os) {
try {
return xmlOutputFactory.createXMLStreamWriter(os);
} catch (XMLStreamException e) {
throw new FailedWriting("Unable to create XMLStreamWriter for " + getName(), e);
return XMLOutputFactory.newInstance().createXMLStreamWriter(os);
} catch (FactoryConfigurationError | XMLStreamException e) {
throw new FailedWriting("Unable to create XMLStreamWriter from " + objectId(os), e);
}
}

Expand All @@ -40,13 +47,39 @@ private static String objectId(Object obj) {
return obj.getClass().getName() + "@" + Integer.toHexString(obj.hashCode());
}

/**
* Register single resource for closing on {@link #close()}.
*/
protected void registerClosable(AutoCloseable close) {
if (this.close != null) throw new IllegalStateException("Unclosed resource already present: " + this.close);

this.close = close;
}

/**
* Close method is called on target every time a transformation using a target is completed.
*
* It closes whatever resource was registered using {@link #registerClosable(AutoCloseable)}, or nothing if not set.
*/
@Override
public void close() throws FailedWriting {
if (close != null) {
try {
close.close();
close = null;
} catch (Exception e) {
throw new FailedWriting("Failed closing target " + getName(), e);
}
}
}

/**
* Target for a file.
*
* The content is flush/closed once the transformation is over. It means that using same File target repeatedly will
* overwrite its content.
*/
class FileTarget implements Target {
static class FileTarget extends Target {
private final File file;

public FileTarget(File file) {
Expand All @@ -63,12 +96,13 @@ public String getName() {
}

@Override
public TransformationHandler getHandler(TransformationBuilder builder, XMLOutputFactory xmlOutputFactory) {
public XMLStreamWriter getWriter(Saxeed saxeed) {
OutputStream os = getOutputStream();
return builder.build(create(xmlOutputFactory, os), os);
registerClosable(os);
return createXmlStreamWriter(os);
}

private OutputStream getOutputStream() throws FailedWriting {
protected /*for testing*/ OutputStream getOutputStream() throws FailedWriting {
try {
return new BufferedOutputStream(new FileOutputStream(file));
} catch (FileNotFoundException e) {
Expand All @@ -82,7 +116,7 @@ private OutputStream getOutputStream() throws FailedWriting {
*
* The stream is NOT closed.
*/
class OutputStreamTarget implements Target {
static class OutputStreamTarget extends Target {
private final OutputStream os;

public OutputStreamTarget(OutputStream os) {
Expand All @@ -95,12 +129,12 @@ public String getName() {
}

@Override
public TransformationHandler getHandler(TransformationBuilder builder, XMLOutputFactory xmlOutputFactory) {
return builder.build(create(xmlOutputFactory, os), null);
public XMLStreamWriter getWriter(Saxeed saxeed) {
return createXmlStreamWriter(os);
}
}

class DevNullTarget extends OutputStreamTarget {
static class DevNullTarget extends OutputStreamTarget {

private static final OutputStream outputStream = new OutputStream() {
@Override
Expand All @@ -119,7 +153,7 @@ public String getName() {
}
}

class XmlStreamWriterTarget implements Target {
static class XmlStreamWriterTarget extends Target {

private final XMLStreamWriter writer;

Expand All @@ -133,8 +167,8 @@ public String getName() {
}

@Override
public TransformationHandler getHandler(TransformationBuilder builder, XMLOutputFactory __) {
return builder.build(writer, null);
public XMLStreamWriter getWriter(Saxeed saxeed) {
return writer;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.github.olivergondza.saxeed.internal.TransformationHandler;

import javax.xml.stream.XMLStreamWriter;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -37,7 +36,7 @@ public TransformationBuilder add(Subscribed subs, Collection<UpdatingVisitor> vi
return this;
}

public TransformationHandler build(XMLStreamWriter writer, AutoCloseable closeAction) {
return new TransformationHandler(visitors, writer, closeAction);
public TransformationHandler build(Saxeed saxeed, Target target) {
return new TransformationHandler(saxeed, target, visitors);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.github.olivergondza.saxeed.internal;

import com.github.olivergondza.saxeed.Saxeed;
import com.github.olivergondza.saxeed.Subscribed;
import com.github.olivergondza.saxeed.TagName;
import com.github.olivergondza.saxeed.Target;
import com.github.olivergondza.saxeed.UpdatingVisitor;
import com.github.olivergondza.saxeed.ex.FailedTransforming;
import com.github.olivergondza.saxeed.ex.FailedWriting;
Expand Down Expand Up @@ -36,8 +38,7 @@ public class TransformationHandler extends DefaultHandler implements AutoCloseab
private final Map<TagName, List<UpdatingVisitor>> visitorCache = new HashMap<>();

private final XMLStreamWriter writer;
// A resource to close once done. Can be null
private final AutoCloseable closeAction;
private final Target target;

private TagImpl currentTag;
private final CharChunk currentChars = new CharChunk();
Expand All @@ -49,13 +50,12 @@ public class TransformationHandler extends DefaultHandler implements AutoCloseab
private final Map<String, AtomicInteger> writtenBookmarks = new HashMap<>();

public TransformationHandler(
LinkedHashMap<UpdatingVisitor, Subscribed> visitors,
XMLStreamWriter writer,
AutoCloseable closeAction
Saxeed saxeed,
Target target, LinkedHashMap<UpdatingVisitor, Subscribed> visitors
) {
this.visitors = visitors;
this.writer = writer;
this.closeAction = closeAction;
this.target = target;
this.writer = target.getWriter(saxeed);
}

@Override
Expand Down Expand Up @@ -328,19 +328,8 @@ public void endDocument() {
@Override
public void close() throws FailedWriting {
try {
// Make sure the content is written to XMLStreamWriter, no matter if closing the target or not
writer.flush();

// Closing XMLStreamWriter never close the target, but it will be un-writable afterward.
// So closing writer iff the target should be closed.
if (closeAction != null) {
// Hackish: Need to close 2 resources, and need to preserve both the eventual exceptions from close() - exactly what
// an empty try-with-resources would do. But, the XMLStreamWriter does not implement AutoClosable, so a compromise
// approach is needed.
try (closeAction) {
writer.close();
}
}
target.close();
} catch (Exception e) {
throw new FailedWriting("Failed closing stream", e);
}
Expand Down
Loading

0 comments on commit 071ed71

Please sign in to comment.