From abe91295cf9b9004b7a914a5557d611ea0d4ccf7 Mon Sep 17 00:00:00 2001 From: Mark Raynsford Date: Thu, 15 Oct 2020 13:55:16 +0000 Subject: [PATCH] Add JXE integration Affects: https://github.com/io7m/blackthorne/issues/2 --- com.io7m.blackthorne.api/pom.xml | 5 + .../com/io7m/blackthorne/api/BTException.java | 87 ++++++++ .../com/io7m/blackthorne/api/Blackthorne.java | 110 +++++++++- .../io7m/blackthorne/api/package-info.java | 6 +- .../src/main/java/module-info.java | 1 + com.io7m.blackthorne.jxe/pom.xml | 44 ++++ .../io7m/blackthorne/jxe/BlackthorneJXE.java | 202 +++++++++++++++++ .../io7m/blackthorne/jxe/package-info.java | 26 +++ .../src/main/java/module-info.java | 30 +++ com.io7m.blackthorne.tests/pom.xml | 5 + .../blackthorne/tests/BlackthorneTest.java | 205 +++++++++++++++--- .../io7m/blackthorne/tests/unparseable.xml | 5 + pom.xml | 4 +- spotbugs-filter.xml | 14 ++ 14 files changed, 714 insertions(+), 30 deletions(-) create mode 100644 com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/BTException.java create mode 100644 com.io7m.blackthorne.jxe/pom.xml create mode 100644 com.io7m.blackthorne.jxe/src/main/java/com/io7m/blackthorne/jxe/BlackthorneJXE.java create mode 100644 com.io7m.blackthorne.jxe/src/main/java/com/io7m/blackthorne/jxe/package-info.java create mode 100644 com.io7m.blackthorne.jxe/src/main/java/module-info.java create mode 100644 com.io7m.blackthorne.tests/src/test/resources/com/io7m/blackthorne/tests/unparseable.xml diff --git a/com.io7m.blackthorne.api/pom.xml b/com.io7m.blackthorne.api/pom.xml index cbbabfa..b5b5068 100644 --- a/com.io7m.blackthorne.api/pom.xml +++ b/com.io7m.blackthorne.api/pom.xml @@ -41,6 +41,11 @@ org.osgi.annotation.bundle provided + + org.osgi + org.osgi.annotation.versioning + provided + org.immutables value diff --git a/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/BTException.java b/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/BTException.java new file mode 100644 index 0000000..413539c --- /dev/null +++ b/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/BTException.java @@ -0,0 +1,87 @@ +/* + * Copyright © 2020 Mark Raynsford http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.blackthorne.api; + +import java.util.List; +import java.util.Objects; + +/** + * An exception raised by convenience methods. + */ + +public final class BTException extends Exception +{ + private final List errors; + + /** + * @return The parse errors encountered during parsing + */ + + public List errors() + { + return this.errors; + } + + /** + * Construct an exception. + * + * @param inMessage The exception message + * @param inErrors The parse errors + */ + + public BTException( + final String inMessage, + final List inErrors) + { + super(Objects.requireNonNull(inMessage, "message")); + this.errors = Objects.requireNonNull(inErrors, "errors"); + } + + /** + * Construct an exception. + * + * @param inMessage The exception message + * @param inCause The cause + * @param inErrors The parse errors + */ + + public BTException( + final String inMessage, + final Throwable inCause, + final List inErrors) + { + super( + Objects.requireNonNull(inMessage, "message"), + Objects.requireNonNull(inCause, "cause")); + this.errors = Objects.requireNonNull(inErrors, "errors"); + } + + /** + * Construct an exception. + * + * @param inCause The cause + * @param inErrors The parse errors + */ + + public BTException( + final Throwable inCause, + final List inErrors) + { + super(Objects.requireNonNull(inCause, "cause")); + this.errors = Objects.requireNonNull(inErrors, "errors"); + } +} diff --git a/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/Blackthorne.java b/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/Blackthorne.java index 7e39d2e..3024ae9 100644 --- a/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/Blackthorne.java +++ b/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/Blackthorne.java @@ -16,19 +16,36 @@ package com.io7m.blackthorne.api; +import com.io7m.jlexing.core.LexicalPosition; import com.io7m.junreachable.UnreachableCodeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Callable; import java.util.function.Function; +import static com.io7m.blackthorne.api.BTParseErrorType.Severity.ERROR; + /** * Convenience functions. */ public final class Blackthorne { + private static final Logger LOG = + LoggerFactory.getLogger(Blackthorne.class); + private Blackthorne() { throw new UnreachableCodeException(); @@ -198,7 +215,9 @@ public static BTElementHandlerConstructorType forScalarAttribute( Objects.requireNonNull(namespaceURI, "namespaceURI"); Objects.requireNonNull(localName, "localName"); Objects.requireNonNull(parser, "parser"); - return forScalarAttribute(BTQualifiedName.of(namespaceURI, localName), parser); + return forScalarAttribute( + BTQualifiedName.of(namespaceURI, localName), + parser); } /** @@ -224,7 +243,11 @@ public static BTElementHandlerConstructorType> forListMono( Objects.requireNonNull(childElementName, "childElementName"); Objects.requireNonNull(itemHandler, "itemHandler"); return context -> - new BTListMonoHandler<>(elementName, childElementName, itemHandler, ignoreUnrecognized); + new BTListMonoHandler<>( + elementName, + childElementName, + itemHandler, + ignoreUnrecognized); } /** @@ -247,6 +270,87 @@ public static BTElementHandlerConstructorType> forListPoly( { Objects.requireNonNull(elementName, "elementName"); Objects.requireNonNull(itemHandlers, "itemHandlers"); - return context -> new BTListPolyHandler(elementName, itemHandlers, ignoreUnrecognized); + return context -> new BTListPolyHandler( + elementName, + itemHandlers, + ignoreUnrecognized); + } + + /** + * A convenience method to configure and execute a parser. + * + * @param source The source URI + * @param stream The input stream + * @param xmlReaders A supplier of XML readers + * @param rootElements The root element handlers + * @param The type of returned values + * + * @return The parsed value + * + * @throws BTException On parse errors + */ + + public static T parse( + final URI source, + final InputStream stream, + final Callable xmlReaders, + final Map> rootElements) + throws BTException + { + final var errors = + new ArrayList(32); + final var contentHandler = + new BTContentHandler<>(source, errors::add, rootElements); + + try { + final var reader = xmlReaders.call(); + reader.setContentHandler(contentHandler); + reader.setErrorHandler(contentHandler); + + final var inputSource = new InputSource(stream); + inputSource.setPublicId(source.toString()); + + reader.parse(inputSource); + + final var resultOpt = contentHandler.result(); + if (resultOpt.isEmpty()) { + throw new BTException("Parse failed.", new IOException(), errors); + } + + return resultOpt.get(); + } catch (final SAXParseException e) { + LOG.error("error encountered during parsing: ", e); + + final var position = + LexicalPosition.of( + e.getLineNumber(), + e.getColumnNumber(), + Optional.of(source) + ); + + errors.add( + BTParseError.builder() + .setLexical(position) + .setSeverity(ERROR) + .setMessage(e.getMessage()) + .build() + ); + + throw new BTException(e.getMessage(), e, errors); + } catch (final Exception e) { + LOG.error("error encountered during parsing: ", e); + + final var position = + LexicalPosition.of(-1, -1, Optional.of(source)); + + errors.add( + BTParseError.builder() + .setLexical(position) + .setSeverity(ERROR) + .setMessage(e.getMessage()) + .build() + ); + throw new BTException(e.getMessage(), e, errors); + } } } diff --git a/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/package-info.java b/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/package-info.java index d5c04ac..f894c3c 100644 --- a/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/package-info.java +++ b/com.io7m.blackthorne.api/src/main/java/com/io7m/blackthorne/api/package-info.java @@ -18,5 +18,9 @@ * Typed XML stream processing (API) */ -@org.osgi.annotation.bundle.Export +@Export +@Version("1.0.0") package com.io7m.blackthorne.api; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/com.io7m.blackthorne.api/src/main/java/module-info.java b/com.io7m.blackthorne.api/src/main/java/module-info.java index f7fb8de..e0d7235 100644 --- a/com.io7m.blackthorne.api/src/main/java/module-info.java +++ b/com.io7m.blackthorne.api/src/main/java/module-info.java @@ -23,6 +23,7 @@ requires static com.io7m.immutables.style; requires static org.immutables.value; requires static org.osgi.annotation.bundle; + requires static org.osgi.annotation.versioning; requires com.io7m.jaffirm.core; requires com.io7m.jlexing.core; diff --git a/com.io7m.blackthorne.jxe/pom.xml b/com.io7m.blackthorne.jxe/pom.xml new file mode 100644 index 0000000..1bcd6f0 --- /dev/null +++ b/com.io7m.blackthorne.jxe/pom.xml @@ -0,0 +1,44 @@ + + + + 4.0.0 + + + com.io7m.blackthorne + com.io7m.blackthorne + 0.0.6-SNAPSHOT + + + com.io7m.blackthorne.jxe + + com.io7m.blackthorne.jxe + Typed XML stream processing (JXE integration) + http://github.com/io7m/blackthorne + + + + ${project.groupId} + com.io7m.blackthorne.api + ${project.version} + + + + com.io7m.jxe + com.io7m.jxe.core + + + + org.osgi + org.osgi.annotation.bundle + provided + + + org.osgi + org.osgi.annotation.versioning + provided + + + + diff --git a/com.io7m.blackthorne.jxe/src/main/java/com/io7m/blackthorne/jxe/BlackthorneJXE.java b/com.io7m.blackthorne.jxe/src/main/java/com/io7m/blackthorne/jxe/BlackthorneJXE.java new file mode 100644 index 0000000..aed1987 --- /dev/null +++ b/com.io7m.blackthorne.jxe/src/main/java/com/io7m/blackthorne/jxe/BlackthorneJXE.java @@ -0,0 +1,202 @@ +/* + * Copyright © 2020 Mark Raynsford http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package com.io7m.blackthorne.jxe; + +import com.io7m.blackthorne.api.BTElementHandlerConstructorType; +import com.io7m.blackthorne.api.BTException; +import com.io7m.blackthorne.api.BTQualifiedName; +import com.io7m.blackthorne.api.Blackthorne; +import com.io7m.jxe.core.JXEHardenedSAXParsers; +import com.io7m.jxe.core.JXESchemaResolutionMappings; +import com.io7m.jxe.core.JXEXInclude; + +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Path; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import static com.io7m.jxe.core.JXEXInclude.XINCLUDE_DISABLED; + +/** + * Blackthorne JXE integration. + */ + +public final class BlackthorneJXE +{ + private static final JXEHardenedSAXParsers PARSERS = + new JXEHardenedSAXParsers(); + + private BlackthorneJXE() + { + + } + + /** + * Parse a document. + * + * @param source The source URI + * @param stream The input stream + * @param parsers A supplier of JXE hardened parsers + * @param rootElements The root element handlers + * @param baseDirectory The base directory + * @param xinclude The xinclude configuration + * @param schemas The schemas + * @param The type of returned values + * + * @return A parsed value + * + * @throws BTException On parse errors + */ + + public static T parseAll( + final URI source, + final InputStream stream, + final Map> rootElements, + final JXEHardenedSAXParsers parsers, + final Optional baseDirectory, + final JXEXInclude xinclude, + final JXESchemaResolutionMappings schemas) + throws BTException + { + Objects.requireNonNull(source, "source"); + Objects.requireNonNull(stream, "stream"); + Objects.requireNonNull(parsers, "parsers"); + Objects.requireNonNull(rootElements, "rootElements"); + Objects.requireNonNull(baseDirectory, "baseDirectory"); + Objects.requireNonNull(xinclude, "xinclude"); + Objects.requireNonNull(schemas, "schemas"); + + return Blackthorne.parse( + source, + stream, + () -> parsers.createXMLReader(baseDirectory, xinclude, schemas), + rootElements + ); + } + + /** + * Parse a document. A default provider of hardened SAX parsers will be used. + * + * @param source The source URI + * @param stream The input stream + * @param rootElements The root element handlers + * @param baseDirectory The base directory + * @param xinclude The xinclude configuration + * @param schemas The schemas + * @param The type of returned values + * + * @return A parsed value + * + * @throws BTException On parse errors + */ + + public static T parse( + final URI source, + final InputStream stream, + final Map> rootElements, + final Optional baseDirectory, + final JXEXInclude xinclude, + final JXESchemaResolutionMappings schemas) + throws BTException + { + Objects.requireNonNull(source, "source"); + Objects.requireNonNull(stream, "stream"); + Objects.requireNonNull(rootElements, "rootElements"); + Objects.requireNonNull(baseDirectory, "baseDirectory"); + Objects.requireNonNull(xinclude, "xinclude"); + Objects.requireNonNull(schemas, "schemas"); + + return parseAll( + source, stream, rootElements, PARSERS, baseDirectory, xinclude, schemas + ); + } + + /** + * Parse a document. A default provider of hardened SAX parsers will be used. + * No filesystem access is allowed. + * + * @param source The source URI + * @param stream The input stream + * @param rootElements The root element handlers + * @param xinclude The xinclude configuration + * @param schemas The schemas + * @param The type of returned values + * + * @return A parsed value + * + * @throws BTException On parse errors + */ + + public static T parse( + final URI source, + final InputStream stream, + final Map> rootElements, + final JXEXInclude xinclude, + final JXESchemaResolutionMappings schemas) + throws BTException + { + Objects.requireNonNull(source, "source"); + Objects.requireNonNull(stream, "stream"); + Objects.requireNonNull(rootElements, "rootElements"); + Objects.requireNonNull(xinclude, "xinclude"); + Objects.requireNonNull(schemas, "schemas"); + + return parseAll( + source, stream, rootElements, PARSERS, Optional.empty(), xinclude, schemas + ); + } + + /** + * Parse a document. A default provider of hardened SAX parsers will be used. + * No filesystem access is allowed. No XInclude is allowed. + * + * @param source The source URI + * @param stream The input stream + * @param rootElements The root element handlers + * @param schemas The schemas + * @param The type of returned values + * + * @return A parsed value + * + * @throws BTException On parse errors + */ + + public static T parse( + final URI source, + final InputStream stream, + final Map> rootElements, + final JXESchemaResolutionMappings schemas) + throws BTException + { + Objects.requireNonNull(source, "source"); + Objects.requireNonNull(stream, "stream"); + Objects.requireNonNull(rootElements, "rootElements"); + Objects.requireNonNull(schemas, "schemas"); + + return parseAll( + source, + stream, + rootElements, + PARSERS, + Optional.empty(), + XINCLUDE_DISABLED, + schemas + ); + } +} diff --git a/com.io7m.blackthorne.jxe/src/main/java/com/io7m/blackthorne/jxe/package-info.java b/com.io7m.blackthorne.jxe/src/main/java/com/io7m/blackthorne/jxe/package-info.java new file mode 100644 index 0000000..3b312c1 --- /dev/null +++ b/com.io7m.blackthorne.jxe/src/main/java/com/io7m/blackthorne/jxe/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright © 2020 Mark Raynsford http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * Blackthorne JXE integration. + */ + +@Export +@Version("1.0.0") +package com.io7m.blackthorne.jxe; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/com.io7m.blackthorne.jxe/src/main/java/module-info.java b/com.io7m.blackthorne.jxe/src/main/java/module-info.java new file mode 100644 index 0000000..c458cd1 --- /dev/null +++ b/com.io7m.blackthorne.jxe/src/main/java/module-info.java @@ -0,0 +1,30 @@ +/* + * Copyright © 2020 Mark Raynsford http://io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * Blackthorne JXE integration. + */ + +module com.io7m.blackthorne.jxe +{ + requires static org.osgi.annotation.bundle; + requires static org.osgi.annotation.versioning; + + requires com.io7m.blackthorne.api; + requires com.io7m.jxe.core; + + exports com.io7m.blackthorne.jxe; +} \ No newline at end of file diff --git a/com.io7m.blackthorne.tests/pom.xml b/com.io7m.blackthorne.tests/pom.xml index e194dee..71e6eb0 100644 --- a/com.io7m.blackthorne.tests/pom.xml +++ b/com.io7m.blackthorne.tests/pom.xml @@ -27,6 +27,11 @@ com.io7m.blackthorne.api ${project.version} + + ${project.groupId} + com.io7m.blackthorne.jxe + ${project.version} + org.slf4j diff --git a/com.io7m.blackthorne.tests/src/test/java/com/io7m/blackthorne/tests/BlackthorneTest.java b/com.io7m.blackthorne.tests/src/test/java/com/io7m/blackthorne/tests/BlackthorneTest.java index ec5229d..89f7b10 100644 --- a/com.io7m.blackthorne.tests/src/test/java/com/io7m/blackthorne/tests/BlackthorneTest.java +++ b/com.io7m.blackthorne.tests/src/test/java/com/io7m/blackthorne/tests/BlackthorneTest.java @@ -20,10 +20,15 @@ import com.io7m.blackthorne.api.BTElementHandlerConstructorType; import com.io7m.blackthorne.api.BTElementHandlerType; import com.io7m.blackthorne.api.BTElementParsingContextType; +import com.io7m.blackthorne.api.BTException; import com.io7m.blackthorne.api.BTIgnoreUnrecognizedElements; import com.io7m.blackthorne.api.BTParseError; import com.io7m.blackthorne.api.BTQualifiedName; import com.io7m.blackthorne.api.Blackthorne; +import com.io7m.blackthorne.jxe.BlackthorneJXE; +import com.io7m.jxe.core.JXEHardenedSAXParsers; +import com.io7m.jxe.core.JXESchemaDefinition; +import com.io7m.jxe.core.JXESchemaDefinitions; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,15 +41,19 @@ import javax.xml.XMLConstants; import javax.xml.parsers.SAXParserFactory; +import java.io.InputStream; import java.math.BigInteger; import java.net.URI; +import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import static com.io7m.blackthorne.api.BTIgnoreUnrecognizedElements.DO_NOT_IGNORE_UNRECOGNIZED_ELEMENTS; import static com.io7m.blackthorne.api.BTIgnoreUnrecognizedElements.IGNORE_UNRECOGNIZED_ELEMENTS; +import static com.io7m.jxe.core.JXEXInclude.XINCLUDE_DISABLED; /** * Tests for the API. @@ -70,15 +79,26 @@ private static InputSource resource( final String name) throws Exception { - final var url = - BlackthorneTest.class.getResource("/com/io7m/blackthorne/tests/" + name); - + final URL url = resourceURL(name); final var stream = url.openStream(); final var source = new InputSource(stream); source.setPublicId(url.toString()); return source; } + private static URL resourceURL( + final String name) + { + return BlackthorneTest.class.getResource("/com/io7m/blackthorne/tests/" + name); + } + + private static InputStream resourceStream( + final String name) + throws Exception + { + return resourceURL(name).openStream(); + } + @BeforeEach public void testSetup() { @@ -103,7 +123,10 @@ public void testNoSchema() throws Exception { final var handler = - new BTContentHandler(URI.create("urn:text"), this::logError, Map.of()); + new BTContentHandler( + URI.create("urn:text"), + this::logError, + Map.of()); final var reader = createReader(); reader.setContentHandler(handler); @@ -115,7 +138,8 @@ public void testNoSchema() { final var error = this.errors.remove(0); - Assertions.assertTrue(error.message().contains("not allowed as a root element")); + Assertions.assertTrue(error.message().contains( + "not allowed as a root element")); } } @@ -130,7 +154,10 @@ public void testUnknownSchema() throws Exception { final var handler = - new BTContentHandler(URI.create("urn:text"), this::logError, Map.of()); + new BTContentHandler( + URI.create("urn:text"), + this::logError, + Map.of()); final var reader = createReader(); reader.setContentHandler(handler); @@ -142,7 +169,8 @@ public void testUnknownSchema() { final var error = this.errors.remove(0); - Assertions.assertTrue(error.message().contains("not allowed as a root element")); + Assertions.assertTrue(error.message().contains( + "not allowed as a root element")); } } @@ -171,7 +199,8 @@ public void testInvalid0() { final var error = this.errors.remove(0); - Assertions.assertTrue(error.message().contains("not allowed as a root element")); + Assertions.assertTrue(error.message().contains( + "not allowed as a root element")); } } @@ -200,7 +229,8 @@ public void testInvalid1() { final var error = this.errors.remove(0); - Assertions.assertTrue(error.message().contains("does not recognize this element")); + Assertions.assertTrue(error.message().contains( + "does not recognize this element")); } } @@ -232,7 +262,6 @@ public void testInteger0() Assertions.assertEquals(BigInteger.valueOf(23L), handler.result().get()); } - /** * Integer values are parsed correctly. * @@ -244,7 +273,10 @@ public void testIntegerAttr0() throws Exception { final var intAttr = - Blackthorne.forScalarAttribute("urn:tests", "intA", BlackthorneTest::parseIntAttribute); + Blackthorne.forScalarAttribute( + "urn:tests", + "intA", + BlackthorneTest::parseIntAttribute); final var handler = BTContentHandler.builder() @@ -426,7 +458,9 @@ public void testChoices0() { final Map>> handlers = Map.ofEntries( - Map.entry(BTQualifiedName.of("urn:tests", "choices"), ChoicesHandler::new) + Map.entry( + BTQualifiedName.of("urn:tests", "choices"), + ChoicesHandler::new) ); final var handler = @@ -459,7 +493,9 @@ public void testChoices1() { final Map>> handlers = Map.ofEntries( - Map.entry(BTQualifiedName.of("urn:tests", "choices"), ChoicesIgnoringHandler::new) + Map.entry( + BTQualifiedName.of("urn:tests", "choices"), + ChoicesIgnoringHandler::new) ); final var handler = @@ -492,7 +528,9 @@ public void testChoices2() { final Map>> handlers = Map.ofEntries( - Map.entry(BTQualifiedName.of("urn:tests", "choices"), ChoicesIgnoringHandler::new) + Map.entry( + BTQualifiedName.of("urn:tests", "choices"), + ChoicesIgnoringHandler::new) ); final var handler = @@ -740,7 +778,9 @@ public void testHandlersAreFunctors0() final var numbers = handler.result().get(); Assertions.assertEquals(Double.valueOf(46.0), numbers.get(0).doubleValue()); - Assertions.assertEquals(Double.valueOf(50.20), numbers.get(1).doubleValue()); + Assertions.assertEquals( + Double.valueOf(50.20), + numbers.get(1).doubleValue()); Assertions.assertEquals(Double.valueOf(20.0), numbers.get(2).doubleValue()); Assertions.assertEquals(3, numbers.size()); } @@ -863,7 +903,9 @@ private static final class ChoiceHandler implements BTElementHandlerType + private static final class ChoiceIgnoringHandler implements + BTElementHandlerType { private Number result; @@ -997,7 +1055,9 @@ public BTIgnoreUnrecognizedElements onShouldIgnoreUnrecognizedElements( { return Map.ofEntries( Map.entry(BTQualifiedName.of("urn:tests", "byte"), ByteHandler::new), - Map.entry(BTQualifiedName.of("urn:tests", "double"), DoubleHandler::new), + Map.entry( + BTQualifiedName.of("urn:tests", "double"), + DoubleHandler::new), Map.entry(BTQualifiedName.of("urn:tests", "int"), IntHandler::new) ); } @@ -1051,7 +1111,8 @@ public List onElementFinished(final BTElementParsingContextType context) } } - private static final class ChoicesIgnoringHandler implements BTElementHandlerType> + private static final class ChoicesIgnoringHandler implements + BTElementHandlerType> { private List results; @@ -1073,7 +1134,9 @@ public BTIgnoreUnrecognizedElements onShouldIgnoreUnrecognizedElements( final BTElementParsingContextType context) { return Map.ofEntries( - Map.entry(BTQualifiedName.of("urn:tests", "choice"), ChoiceIgnoringHandler::new) + Map.entry( + BTQualifiedName.of("urn:tests", "choice"), + ChoiceIgnoringHandler::new) ); } @@ -1091,4 +1154,96 @@ public List onElementFinished(final BTElementParsingContextType context) return this.results; } } + + /** + * Integer values are parsed correctly. + * + * @throws Exception On errors + */ + + @Test + public void testConvenience0() + throws Exception + { + final Map> handlers = + Map.ofEntries( + Map.entry(BTQualifiedName.of("urn:tests", "int"), IntHandler::new) + ); + + final var mappings = + JXESchemaDefinitions.mappingsOf( + JXESchemaDefinition.of( + URI.create("urn:tests"), + "choice.xsd", + resourceURL("choice.xsd") + )); + + BlackthorneJXE.parseAll( + URI.create("urn:test"), + resourceStream("int.xml"), + handlers, + new JXEHardenedSAXParsers(), + Optional.empty(), + XINCLUDE_DISABLED, + mappings + ); + + BlackthorneJXE.parse( + URI.create("urn:test"), + resourceStream("int.xml"), + handlers, + Optional.empty(), + XINCLUDE_DISABLED, + mappings + ); + + BlackthorneJXE.parse( + URI.create("urn:test"), + resourceStream("int.xml"), + handlers, + XINCLUDE_DISABLED, + mappings + ); + + BlackthorneJXE.parse( + URI.create("urn:test"), + resourceStream("int.xml"), + handlers, + mappings + ); + } + + /** + * Unparseable documents raise exceptions. + * + * @throws Exception On errors + */ + + @Test + public void testConvenience1() + throws Exception + { + final Map> handlers = + Map.ofEntries( + Map.entry(BTQualifiedName.of("urn:tests", "int"), IntHandler::new) + ); + + final var ex = + Assertions.assertThrows(BTException.class, () -> { + Blackthorne.parse( + URI.create("urn:test"), + resourceStream("unparseable.xml"), + () -> { + return new JXEHardenedSAXParsers() + .createXMLReaderNonValidating( + Optional.empty(), + XINCLUDE_DISABLED); + }, + handlers + ); + }); + + Assertions.assertEquals(SAXParseException.class, ex.getCause().getClass()); + Assertions.assertEquals(2, ex.errors().size()); + } } diff --git a/com.io7m.blackthorne.tests/src/test/resources/com/io7m/blackthorne/tests/unparseable.xml b/com.io7m.blackthorne.tests/src/test/resources/com/io7m/blackthorne/tests/unparseable.xml new file mode 100644 index 0000000..94176a2 --- /dev/null +++ b/com.io7m.blackthorne.tests/src/test/resources/com/io7m/blackthorne/tests/unparseable.xml @@ -0,0 +1,5 @@ + + + com.io7m.blackthorne.api com.io7m.blackthorne.tests + com.io7m.blackthorne.jxe 0b143cd2902b46e19be55cc6933e2362 2.7.5 5.7.0 + 1.0.0-SNAPSHOT 0.0.1-SNAPSHOT @@ -133,7 +135,7 @@ com.io7m.jxe com.io7m.jxe.core - 0.0.2 + ${com.io7m.jxe.version} com.io7m.jaffirm diff --git a/spotbugs-filter.xml b/spotbugs-filter.xml index 0d43732..2fc7d84 100644 --- a/spotbugs-filter.xml +++ b/spotbugs-filter.xml @@ -61,10 +61,24 @@ + + + + + + + + + + + + + +