diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java index 3ab8cea04..a2517c02a 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java @@ -105,43 +105,23 @@ public String close(final Object[] args) { private static final int TAG_BEGIN = 0; private static final int TAG_END = 3; - private static boolean isInstanced; + private final Encoder encoder = new Encoder(); + private final Marc21Decoder decoder = new Marc21Decoder(); + private final Marc21Encoder wrapper = new Marc21Encoder(); - private final StringBuilder builder = new StringBuilder(); + private DefaultStreamPipe> pipe; - private final StringBuilder builderLeader = new StringBuilder(); - private boolean atStreamStart = true; - - private boolean omitXmlDeclaration = OMIT_XML_DECLARATION; - private String xmlVersion = XML_VERSION; - private String xmlEncoding = XML_ENCODING; - - private String currentEntity = ""; - - private boolean emitNamespace = true; - private Object[] namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; - - private int indentationLevel; - private boolean formatted = PRETTY_PRINTED; - private int recordAttributeOffset; - - private boolean ensureCorrectMarc21Xml = ENSURE_CORRECT_MARC21_XML; - private final Marc21Decoder marc21Decoder = new Marc21Decoder(); - private final Marc21Encoder marc21Encoder = new Marc21Encoder(); - private MarcXmlEncoder marcXmlEncoder; /** * Creates an instance of {@link MarcXmlEncoder}. */ - public MarcXmlEncoder() { - if (!isInstanced) { - isInstanced = true; - marcXmlEncoder = new MarcXmlEncoder(); - marc21Decoder.setEmitLeaderAsWhole(true); + decoder.setEmitLeaderAsWhole(true); - marc21Encoder.setReceiver(marc21Decoder); - marc21Decoder.setReceiver(marcXmlEncoder); - } + wrapper + .setReceiver(decoder) + .setReceiver(encoder); + + setEnsureCorrectMarc21Xml(ENSURE_CORRECT_MARC21_XML); } /** @@ -151,52 +131,41 @@ public MarcXmlEncoder() { * @param emitNamespace true if the namespace is emitted, otherwise false */ public void setEmitNamespace(final boolean emitNamespace) { - this.emitNamespace = emitNamespace; - namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; - if (marcXmlEncoder != null) { - marcXmlEncoder.setEmitNamespace(emitNamespace); - } + encoder.setEmitNamespace(emitNamespace); } /** * Sets the flag to decide whether to omit the XML declaration. + * * Default value: {@value #OMIT_XML_DECLARATION} * * @param currentOmitXmlDeclaration true if the XML declaration is omitted, otherwise * false */ public void omitXmlDeclaration(final boolean currentOmitXmlDeclaration) { - omitXmlDeclaration = currentOmitXmlDeclaration; - if (marcXmlEncoder != null) { - marcXmlEncoder.omitXmlDeclaration(currentOmitXmlDeclaration); - } + encoder.omitXmlDeclaration(currentOmitXmlDeclaration); } /** * Sets the XML version. + * * Default value: {@value #XML_VERSION} * * @param xmlVersion the XML version */ public void setXmlVersion(final String xmlVersion) { - this.xmlVersion = xmlVersion; - this.xmlVersion = xmlVersion; - if (marcXmlEncoder != null) { - marcXmlEncoder.setXmlVersion(xmlVersion); - } + encoder.setXmlVersion(xmlVersion); } /** * Sets the XML encoding. + * * Default value: {@value #XML_ENCODING} * * @param xmlEncoding the XML encoding */ public void setXmlEncoding(final String xmlEncoding) { - this.xmlEncoding = xmlEncoding; - if (marcXmlEncoder != null) { - marcXmlEncoder.setXmlEncoding(xmlEncoding); - } + encoder.setXmlEncoding(xmlEncoding); } /** @@ -210,41 +179,106 @@ public void setXmlEncoding(final String xmlEncoding) { * @param ensureCorrectMarc21Xml if true the input data is validated to ensure correct MARC21. Also the leader may be generated. */ public void setEnsureCorrectMarc21Xml(final boolean ensureCorrectMarc21Xml) { - this.ensureCorrectMarc21Xml = ensureCorrectMarc21Xml; - isInstanced = true; - marcXmlEncoder = new MarcXmlEncoder(); - marc21Decoder.setEmitLeaderAsWhole(true); - - marc21Encoder.setReceiver(marc21Decoder); - marc21Decoder.setReceiver(marcXmlEncoder); + pipe = ensureCorrectMarc21Xml ? wrapper : encoder; } /** * Formats the resulting xml by indentation. Aka "pretty printing". + * * Default value: {@value #PRETTY_PRINTED} * * @param formatted true if formatting is activated, otherwise false */ public void setFormatted(final boolean formatted) { - this.formatted = formatted; - if (marcXmlEncoder != null) { - marcXmlEncoder.setFormatted(formatted); - } + encoder.setFormatted(formatted); } @Override - protected void onSetReceiver() { - if (marcXmlEncoder != null) { - marcXmlEncoder.setReceiver(getReceiver()); - } + public void startRecord(final String identifier) { + pipe.startRecord(identifier); } @Override - public void startRecord(final String identifier) { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.startRecord(identifier); + public void endRecord() { + pipe.endRecord(); + } + + @Override + public void startEntity(final String name) { + pipe.startEntity(name); + } + + @Override + public void endEntity() { + pipe.endEntity(); + } + + @Override + public void literal(final String name, final String value) { + pipe.literal(name, value); + } + + @Override + protected void onResetStream() { + pipe.resetStream(); + } + + @Override + protected void onCloseStream() { + pipe.closeStream(); + } + + @Override + protected void onSetReceiver() { + encoder.setReceiver(getReceiver()); + } + + private static class Encoder extends DefaultStreamPipe> { + + private final StringBuilder builder = new StringBuilder(); + private final StringBuilder leaderBuilder = new StringBuilder(); + + private boolean atStreamStart = true; + + private boolean omitXmlDeclaration = OMIT_XML_DECLARATION; + private String xmlVersion = XML_VERSION; + private String xmlEncoding = XML_ENCODING; + + private String currentEntity = ""; + + private boolean emitNamespace = true; + private Object[] namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; + + private int indentationLevel; + private boolean formatted = PRETTY_PRINTED; + private int recordAttributeOffset; + + private Encoder() { + } + + public void setEmitNamespace(final boolean emitNamespace) { + this.emitNamespace = emitNamespace; + namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; + } + + public void omitXmlDeclaration(final boolean currentOmitXmlDeclaration) { + omitXmlDeclaration = currentOmitXmlDeclaration; + } + + public void setXmlVersion(final String xmlVersion) { + this.xmlVersion = xmlVersion; + } + + public void setXmlEncoding(final String xmlEncoding) { + this.xmlEncoding = xmlEncoding; } - else { + + public void setFormatted(final boolean formatted) { + this.formatted = formatted; + } + + @Override + public void startRecord(final String identifier) { if (atStreamStart) { if (!omitXmlDeclaration) { writeHeader(); @@ -263,15 +297,10 @@ public void startRecord(final String identifier) { incrementIndentationLevel(); } - } - @Override - public void endRecord() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.endRecord(); - } - else { - if (builderLeader.length() > 0) { + @Override + public void endRecord() { + if (leaderBuilder.length() > 0) { writeLeader(); } decrementIndentationLevel(); @@ -280,14 +309,9 @@ public void endRecord() { prettyPrintNewLine(); sendAndClearData(); } - } - @Override - public void startEntity(final String name) { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.startEntity(name); - } - else { + @Override + public void startEntity(final String name) { currentEntity = name; if (!name.equals(Marc21EventNames.LEADER_ENTITY)) { if (name.length() != LEADER_ENTITY_LENGTH) { @@ -305,14 +329,9 @@ public void startEntity(final String name) { incrementIndentationLevel(); } } - } - @Override - public void endEntity() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.endEntity(); - } - else { + @Override + public void endEntity() { if (!currentEntity.equals(Marc21EventNames.LEADER_ENTITY)) { decrementIndentationLevel(); prettyPrintIndentation(); @@ -321,14 +340,9 @@ public void endEntity() { } currentEntity = ""; } - } - @Override - public void literal(final String name, final String value) { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.literal(name, value); - } - else { + @Override + public void literal(final String name, final String value) { if ("".equals(currentEntity)) { if (name.equals(Marc21EventNames.MARCXML_TYPE_LITERAL)) { if (value != null) { @@ -353,126 +367,112 @@ else if (!appendLeader(currentEntity, value)) { prettyPrintNewLine(); } } - } - @Override - protected void onResetStream() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.resetStream(); - } - else { + @Override + protected void onResetStream() { if (!atStreamStart) { writeFooter(); } sendAndClearData(); atStreamStart = true; } - } - @Override - protected void onCloseStream() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.closeStream(); - } - else { + @Override + protected void onCloseStream() { writeFooter(); sendAndClearData(); } - } - - /** Increments the indentation level by one */ - private void incrementIndentationLevel() { - indentationLevel += 1; - } - - /** Decrements the indentation level by one */ - private void decrementIndentationLevel() { - indentationLevel -= 1; - } - /** Adds a XML Header */ - private void writeHeader() { - writeRaw(String.format(XML_DECLARATION_TEMPLATE, xmlVersion, xmlEncoding)); - } + /** Increments the indentation level by one */ + private void incrementIndentationLevel() { + indentationLevel += 1; + } - /** Closes the root tag */ - private void writeFooter() { - writeTag(Tag.collection::close); - } + /** Decrements the indentation level by one */ + private void decrementIndentationLevel() { + indentationLevel -= 1; + } - /** - * Writes an unescaped sequence. - * - * @param str the unescaped sequence to be written - */ - private void writeRaw(final String str) { - builder.append(str); - } + /** Adds a XML Header */ + private void writeHeader() { + writeRaw(String.format(XML_DECLARATION_TEMPLATE, xmlVersion, xmlEncoding)); + } - /** - * Writes an unescaped sequence to the leader literal. - * - * @param str the unescaped sequence to be written - */ - private void appendLeader(final String str) { - builderLeader.append(str); - } + /** Closes the root tag */ + private void writeFooter() { + writeTag(Tag.collection::close); + } - private boolean appendLeader(final String name, final String value) { - if (name.equals(Marc21EventNames.LEADER_ENTITY)) { - appendLeader(value); - return true; + /** + * Writes an unescaped sequence. + * + * @param str the unescaped sequence to be written + */ + private void writeRaw(final String str) { + builder.append(str); } - else { - return false; + + /** + * Writes an unescaped sequence to the leader literal. + * + * @param str the unescaped sequence to be written + */ + private void appendLeader(final String str) { + leaderBuilder.append(str); } - } - /** - * Writes an escaped sequence. - * - * @param str the unescaped sequence to be written - */ - private void writeEscaped(final String str) { - builder.append(XmlUtil.escape(str, false)); - } + private boolean appendLeader(final String name, final String value) { + if (name.equals(Marc21EventNames.LEADER_ENTITY)) { + appendLeader(value); + return true; + } + else { + return false; + } + } - private void writeLeader() { - prettyPrintIndentation(); - writeTag(Tag.leader::open); - writeRaw(builderLeader.toString()); - writeTag(Tag.leader::close); - prettyPrintNewLine(); - } + /** + * Writes an escaped sequence. + * + * @param str the unescaped sequence to be written + */ + private void writeEscaped(final String str) { + builder.append(XmlUtil.escape(str, false)); + } - private void writeTag(final Function function, final Object... args) { - final Object[] allArgs = Arrays.copyOf(namespacePrefix, namespacePrefix.length + args.length); - System.arraycopy(args, 0, allArgs, namespacePrefix.length, args.length); - writeRaw(function.apply(allArgs)); - } + private void writeLeader() { + prettyPrintIndentation(); + writeTag(Tag.leader::open); + writeRaw(leaderBuilder.toString()); + writeTag(Tag.leader::close); + prettyPrintNewLine(); + } - private void prettyPrintIndentation() { - if (formatted) { - final String prefix = String.join("", Collections.nCopies(indentationLevel, INDENT)); - builder.append(prefix); + private void writeTag(final Function function, final Object... args) { + final Object[] allArgs = Arrays.copyOf(namespacePrefix, namespacePrefix.length + args.length); + System.arraycopy(args, 0, allArgs, namespacePrefix.length, args.length); + writeRaw(function.apply(allArgs)); } - } - private void prettyPrintNewLine() { - if (formatted) { - builder.append(NEW_LINE); + private void prettyPrintIndentation() { + if (formatted) { + final String prefix = String.join("", Collections.nCopies(indentationLevel, INDENT)); + builder.append(prefix); + } } - } - private void sendAndClearData() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marcXmlEncoder.sendAndClearData(); + private void prettyPrintNewLine() { + if (formatted) { + builder.append(NEW_LINE); + } } - else { + + private void sendAndClearData() { getReceiver().process(builder.toString()); builder.delete(0, builder.length()); recordAttributeOffset = 0; } + } } diff --git a/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java b/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java index fa258cf29..7d7b97364 100644 --- a/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java +++ b/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java @@ -16,24 +16,17 @@ package org.metafacture.biblio.marc21; -import org.junit.After; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import org.junit.Before; -import org.junit.Test; -import static org.metafacture.biblio.marc21.Marc21EventNames.BIBLIOGRAPHIC_LEVEL_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.CATALOGING_FORM_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.CHARACTER_CODING_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.ENCODING_LEVEL_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.MULTIPART_LEVEL_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.RECORD_STATUS_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.RECORD_TYPE_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.TYPE_OF_CONTROL_LITERAL; import org.metafacture.framework.FormatException; import org.metafacture.framework.MetafactureException; import org.metafacture.framework.MissingIdException; import org.metafacture.framework.helpers.DefaultObjectReceiver; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * Tests for class {@link MarcXmlEncoder}. * @@ -56,34 +49,24 @@ public class MarcXmlEncoderTest { private static final String XML_MARC_COLLECTION_END_TAG = ""; private static final String RECORD_ID = "92005291"; - private static StringBuilder resultCollector; - private final MarcXmlEncoder encoder = new MarcXmlEncoder(); - private final MarcXmlEncoder encoder_ensureCorrectMarc21Xml = new MarcXmlEncoder(); - + private static StringBuilder resultCollector = new StringBuilder(); + private static MarcXmlEncoder encoder; @Before public void setUp() { - initializeEncoder(encoder); - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - initializeEncoder(encoder_ensureCorrectMarc21Xml); - } - - private void initializeEncoder(MarcXmlEncoder enc) { - enc.setFormatted(false); - enc.setReceiver(new DefaultObjectReceiver() { + encoder = new MarcXmlEncoder(); + encoder.setFormatted(false); + encoder.setReceiver(new DefaultObjectReceiver() { @Override public void process(final String obj) { resultCollector.append(obj); } }); - resultCollector = new StringBuilder(); - } - @After - public void tearDown() { + resultCollector.delete(0, resultCollector.length()); } - private void addOneRecord(MarcXmlEncoder encoder) { + private void addOneRecord(final MarcXmlEncoder encoder) { encoder.startRecord(RECORD_ID); encoder.literal("001", RECORD_ID); encoder.startEntity("010 "); @@ -209,17 +192,17 @@ public void createAnRecordWithLeader(){ @Test(expected = FormatException.class) public void createAnRecordWithLeader_ensureCorrectMarc21Xml() { - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - createAnRecordWithLeader(encoder_ensureCorrectMarc21Xml); + encoder.setEnsureCorrectMarc21Xml(true); + createAnRecordWithLeader(encoder); } - private void createAnRecordWithLeader(MarcXmlEncoder enc) { - enc.startRecord("1"); - enc.startEntity(Marc21EventNames.LEADER_ENTITY); - enc.literal(Marc21EventNames.LEADER_ENTITY, "dummy"); - enc.endEntity(); - enc.endRecord(); - enc.closeStream(); + private void createAnRecordWithLeader(final MarcXmlEncoder encoder) { + encoder.startRecord("1"); + encoder.startEntity(Marc21EventNames.LEADER_ENTITY); + encoder.literal(Marc21EventNames.LEADER_ENTITY, "dummy"); + encoder.endEntity(); + encoder.endRecord(); + encoder.closeStream(); String expected = XML_DECLARATION + XML_ROOT_OPEN + "dummy" + XML_MARC_COLLECTION_END_TAG; String actual = resultCollector.toString(); @@ -239,13 +222,14 @@ public void issue336_createRecordWithTopLevelLeader() { } @Test - public void issue336_createRecordWithTopLevelLeader_ensureCorrectMarc21Xml() { - createRecordWithTopLevelLeader(encoder_ensureCorrectMarc21Xml, "00048naa a2200037uc 4500"); + public void issue336_createRecordWithTopLevelLeader_defaultMarc21Xml() { + createRecordWithTopLevelLeader(encoder, "00000naa a2200000uc 4500"); } @Test - public void issue336_createRecordWithTopLevelLeader_defaultMarc21Xml() { - createRecordWithTopLevelLeader(encoder,"00000naa a2200000uc 4500"); + public void issue336_createRecordWithTopLevelLeader_ensureCorrectMarc21Xml() { + encoder.setEnsureCorrectMarc21Xml(true); + createRecordWithTopLevelLeader(encoder, "00048naa a2200037uc 4500"); } private void createRecordWithTopLevelLeader(final MarcXmlEncoder encoder, final String expectedLeader) { @@ -261,12 +245,6 @@ private void createRecordWithTopLevelLeader(final MarcXmlEncoder encoder, final assertEquals(expected, actual); } - @Test(expected = NullPointerException.class) - public void ensureCorrectMarc21XmlParameterAfterSettingReceiver() { - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - createRecordWithTopLevelLeader(encoder_ensureCorrectMarc21Xml,"ignored"); - } - @Test public void issue527ShouldEmitLeaderAlwaysAsWholeString() { issue527ShouldEmitLeaderAlwaysAsWholeString(encoder); @@ -274,21 +252,21 @@ public void issue527ShouldEmitLeaderAlwaysAsWholeString() { @Test(expected = MissingIdException.class) public void issue527ShouldEmitLeaderAlwaysAsWholeString_ensureCorrectMarc21Xml() { - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - this.issue527ShouldEmitLeaderAlwaysAsWholeString(encoder_ensureCorrectMarc21Xml); + encoder.setEnsureCorrectMarc21Xml(true); + issue527ShouldEmitLeaderAlwaysAsWholeString(encoder); } private void issue527ShouldEmitLeaderAlwaysAsWholeString(MarcXmlEncoder encoder) { encoder.startRecord("1"); encoder.startEntity(Marc21EventNames.LEADER_ENTITY); - encoder.literal(RECORD_STATUS_LITERAL, "a"); - encoder.literal(RECORD_TYPE_LITERAL, "o"); - encoder.literal(BIBLIOGRAPHIC_LEVEL_LITERAL, "a"); - encoder.literal(TYPE_OF_CONTROL_LITERAL, " "); - encoder.literal(CHARACTER_CODING_LITERAL, "a"); - encoder.literal(ENCODING_LEVEL_LITERAL, "z"); - encoder.literal(CATALOGING_FORM_LITERAL, "u"); - encoder.literal(MULTIPART_LEVEL_LITERAL, " "); + encoder.literal(Marc21EventNames.RECORD_STATUS_LITERAL, "a"); + encoder.literal(Marc21EventNames.RECORD_TYPE_LITERAL, "o"); + encoder.literal(Marc21EventNames.BIBLIOGRAPHIC_LEVEL_LITERAL, "a"); + encoder.literal(Marc21EventNames.TYPE_OF_CONTROL_LITERAL, " "); + encoder.literal(Marc21EventNames.CHARACTER_CODING_LITERAL, "a"); + encoder.literal(Marc21EventNames.ENCODING_LEVEL_LITERAL, "z"); + encoder.literal(Marc21EventNames.CATALOGING_FORM_LITERAL, "u"); + encoder.literal(Marc21EventNames.MULTIPART_LEVEL_LITERAL, " "); encoder.endEntity(); encoder.endRecord(); encoder.closeStream(); @@ -298,8 +276,6 @@ private void issue527ShouldEmitLeaderAlwaysAsWholeString(MarcXmlEncoder encoder) assertEquals(expected, actual); } - - @Test public void sendDataAndClearWhenRecordStartedAndStreamResets() { encoder.startRecord("1");