Author: desruisseaux Date: Tue Feb 13 18:02:12 2018 New Revision: 1824166 URL: http://svn.apache.org/viewvc?rev=1824166&view=rev Log: Replace the use of XMLStreamReader by XMLEventReader.
Added: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredXML.java (with props) Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/InputFactory.java sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledUnmarshaller.java Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java?rev=1824166&r1=1824165&r2=1824166&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilterVersion.java [UTF-8] Tue Feb 13 18:02:12 2018 @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Collections; import javax.xml.namespace.QName; +import org.apache.sis.util.Debug; import org.apache.sis.internal.jaxb.LegacyNamespaces; @@ -153,6 +154,14 @@ final class FilterVersion { final String exportProperty(final String localPart) { return exports.getOrDefault(localPart, localPart); } + + /** + * Returns the namespace for debugging purpose. + */ + @Override @Debug + public String toString() { + return namespace; + } } /** @@ -241,6 +250,10 @@ final class FilterVersion { uri = r.namespace; name = new QName(uri, r.exportProperty(name.getLocalPart()), Namespaces.getPreferredPrefix(uri, name.getPrefix())); + /* + * Note: the call for 'getPreferredPrefix' above is required: + * JAXB seems to need the prefixes for recognizing namespaces. + */ } } return name; Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java?rev=1824166&r1=1824165&r2=1824166&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java [UTF-8] Tue Feb 13 18:02:12 2018 @@ -207,7 +207,7 @@ abstract class FilteredEvent<E extends X * This wrapper is used for changing the namespace and sometime the name of the element. * The attributes may also be modified. */ - static final class Start extends FilteredEvent<StartElement> implements StartElement { + static class Start extends FilteredEvent<StartElement> implements StartElement { /** The namespaces, may or may not be the same than the wrapped event. */ private final List<Namespace> namespaces; @@ -215,7 +215,7 @@ abstract class FilteredEvent<E extends X private final List<Attribute> attributes; /** The version to export, used for wrapping namespace context. */ - private final FilterVersion version; + final FilterVersion version; /** Wraps the given event with potentially different name, namespaces and attributes. */ Start(StartElement event, QName name, List<Namespace> namespaces, List<Attribute> attributes, FilterVersion version) { @@ -225,18 +225,18 @@ abstract class FilteredEvent<E extends X this.version = version; } - @Override public boolean isStartElement() {return true;} - @Override public StartElement asStartElement() {return this;} - @Override public int getEventType() {return START_ELEMENT;} - @Override public Iterator<Namespace> getNamespaces() {return namespaces.iterator();} - @Override public Iterator<Attribute> getAttributes() {return attributes.iterator();} + @Override public final boolean isStartElement() {return true;} + @Override public final StartElement asStartElement() {return this;} + @Override public final int getEventType() {return START_ELEMENT;} + @Override public final Iterator<Namespace> getNamespaces() {return namespaces.iterator();} + @Override public final Iterator<Attribute> getAttributes() {return attributes.iterator();} /** * Returns the attribute referred to by the given name, or {@code null} if none. * Current implementation is okay on the assumption that there is few attributes. */ @Override - public Attribute getAttributeByName(final QName name) { + public final Attribute getAttributeByName(final QName name) { for (final Attribute attr : attributes) { if (name.equals(attr.getName())) { return attr; @@ -246,7 +246,8 @@ abstract class FilteredEvent<E extends X } /** - * Gets a read-only namespace context. + * Gets a read-only namespace context. Default implementation is suitable for exports + * (i.e. write operation). Needs to be overridden for imports (read operation). * * @see FilteredWriter#getNamespaceContext() */ @@ -257,6 +258,8 @@ abstract class FilteredEvent<E extends X /** * Gets the value that the prefix is bound to in the context of this element. + * Default implementation is suitable for export (i.e. write operation). + * Needs to be overridden for imports (read operation). */ @Override public String getNamespaceURI(final String prefix) { @@ -267,7 +270,7 @@ abstract class FilteredEvent<E extends X * Writes the event as per the XML 1.0 without indentation or whitespace. */ @Override - void write(final Appendable out) throws IOException { + final void write(final Appendable out) throws IOException { name(out.append('<')); final int n = attributes.size(); for (int i=0; i<n; i++) { @@ -277,4 +280,30 @@ abstract class FilteredEvent<E extends X out.append('>'); } } + + /** + * Wrapper over an element emitted during the reading of an XML document. + */ + static final class Import extends Start { + /** Wraps the given event with potentially different name, namespaces and attributes. */ + Import(StartElement event, QName name, List<Namespace> namespaces, List<Attribute> attributes, FilterVersion version) { + super(event, name, namespaces, attributes, version); + } + + /** + * Gets a read-only namespace context. + */ + @Override + public NamespaceContext getNamespaceContext() { + return FilteredNamespaces.importNS(event.getNamespaceContext(), version); + } + + /** + * Gets the value that the prefix is bound to in the context of this element. + */ + @Override + public String getNamespaceURI(final String prefix) { + return version.importNS(event.getNamespaceURI(prefix)); + } + } } Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java?rev=1824166&r1=1824165&r2=1824166&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredReader.java [UTF-8] Tue Feb 13 18:02:12 2018 @@ -22,23 +22,30 @@ import java.util.List; import java.util.HashSet; import java.util.HashMap; import java.util.ArrayList; +import java.util.Iterator; +import java.util.Collections; +import java.util.InvalidPropertiesFormatException; import java.io.IOException; import java.io.LineNumberReader; import java.io.InputStreamReader; -import java.util.InvalidPropertiesFormatException; import javax.xml.namespace.QName; -import javax.xml.namespace.NamespaceContext; import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; -import javax.xml.stream.util.StreamReaderDelegate; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Namespace; +import javax.xml.stream.events.EndElement; +import javax.xml.stream.events.StartElement; import org.apache.sis.util.CharSequences; import org.apache.sis.util.resources.Errors; import org.apache.sis.internal.util.CollectionsExt; +import static javax.xml.stream.XMLStreamConstants.*; + /** * A filter replacing the namespaces found in XML documents by the namespaces expected by SIS at unmarshalling time. - * This class forwards every method calls to the wrapped {@link XMLStreamReader}, but with some {@code namespaceURI} + * This class forwards every method calls to the wrapped {@link XMLEventReader}, but with some {@code namespaceURI} * modified before being transfered. This class uses a dictionary for identifying the XML namespaces expected by JAXB * implementation. This is needed when a single namespace in a legacy schema has been splitted into many namespaces * in the newer schema. This happen for example in the upgrade from ISO 19139:2007 to ISO 19115-3. @@ -50,7 +57,7 @@ import org.apache.sis.internal.util.Coll * @since 1.0 * @module */ -final class FilteredReader extends StreamReaderDelegate { +final class FilteredReader extends FilteredXML implements XMLEventReader { /** * Location of the file listing types and attributes contained in namespaces. * The file location is relative to this {@code NamespaceContent} class. @@ -164,6 +171,18 @@ final class FilteredReader extends Strea } /** + * Returns the namespace for the given ISO type, or {@code null} if unknown. + * This is the namespace used in JAXB annotations. + * + * @param type a class name defined by ISO 19115 or related standards (e.g. {@code "CI_Citation"}. + * @return a namespace for the given type, or {@code null} if unknown. + */ + static String namespace(final String type) { + final Map<String,String> attributes = NAMESPACES.get(type); + return (attributes != null) ? attributes.get(TYPE_KEY) : null; + } + + /** * The mapping from attribute names to types where such attribute is declared. * An attribute of the same name may be declared in many types. * @@ -194,105 +213,146 @@ final class FilteredReader extends Strea } /** - * The external XML format version to unmarshal from. + * The reader from which to read events. */ - private final FilterVersion version; + private final XMLEventReader in; /** - * List of encountered XML tags, in order. Used for backtracking. - * Elements are removed from this list when they are closed. + * List of encountered XML tags, in order. Used for backtracking. Elements are removed from this list + * when they are closed. Names should be the ones we get after conversion from namespaces used in XML + * document to namespaces used in JAXB annotations. */ - private final List<String> outerElements; + private final List<QName> outerElements; /** - * The {@code localPart} argument given to {@link #namespaceOf(String)}, potentially renamed. - * For example given the {@code "DCP/distributedComputingPlatform"} entry in {@value #FILENAME} file, - * a call to {@code namespaceOf("DCP")} will set this field to {@code "distributedComputingPlatform"}. + * Creates a new filter for the given version of the standards. */ - private String renamed; + FilteredReader(final XMLEventReader in, final FilterVersion version) { + super(version); + this.in = in; + outerElements = new ArrayList<>(); + } /** - * Creates a new filter for the given version of the standards. + * Checks if there are more events. */ - FilteredReader(final XMLStreamReader in, final FilterVersion version) { - super(in); - this.version = version; - outerElements = new ArrayList<>(); + @Override + public boolean hasNext() { + return in.hasNext(); } /** - * Forwards the call and keep trace of the XML element opened up to this point. + * Check the next XMLEvent without reading it from the stream. */ @Override - public int next() throws XMLStreamException { - return traceElements(super.next()); + public XMLEvent peek() throws XMLStreamException { + return convert(in.peek(), false); } /** - * Forwards the call and keep trace of the XML element opened up to this point. + * Returns the next element. Use {@link #nextEvent()} instead. */ @Override - public int nextTag() throws XMLStreamException { - return traceElements(super.nextTag()); + public Object next() { + return convert((XMLEvent) in.next(), true); } /** - * Keeps trace of XML elements opened up to this point. - * - * @param type value of {@link #getEventType()}. - * @return {@code type}, returned for convenience. + * Forwards the call and keep trace of the XML elements opened up to this point. */ - private int traceElements(final int type) { - switch (type) { + @Override + public XMLEvent nextEvent() throws XMLStreamException { + return convert(in.nextEvent(), true); + } + + /** + * Forwards the call and keep trace of the XML elements opened up to this point. + */ + @Override + public XMLEvent nextTag() throws XMLStreamException { + return convert(in.nextTag(), true); + } + + /** + * Keeps trace of XML elements opened up to this point and imports the given event. + * This method replaces the namespaces used in XML document by the namespace used by JAXB annotations. + * + * @param event the event read from the underlying event reader. + * @param next {@code true} for a {@code next} operation, or {@code false} for a {@code peek} operation. + * @return the converted event (may be the same instance). + */ + @SuppressWarnings("unchecked") // TODO: remove on JDK9 + private XMLEvent convert(XMLEvent event, final boolean next) { + switch (event.getEventType()) { + case ATTRIBUTE: { + event = convert((Attribute) event); + break; + } + case NAMESPACE: { + event = importNS((Namespace) event); + break; + } case START_ELEMENT: { - outerElements.add(getLocalName()); + final StartElement e = event.asStartElement(); + final QName originalName = e.getName(); + final QName name = convert(originalName); + boolean changed = name != originalName; + for (final Iterator<Attribute> it = e.getAttributes(); it.hasNext();) { + final Attribute a = it.next(); + final Attribute ae = convert(a); + changed |= (a != ae); + renamedAttributes.add(ae); + } + final List<Namespace> namespaces = importNS(e.getNamespaces(), changed); + if (namespaces != null) { + event = new FilteredEvent.Import(e, name, namespaces, attributes(), version); + } else { + renamedAttributes.clear(); + } + if (next) { + outerElements.add(e.getName()); + } break; } case END_ELEMENT: { + final EndElement e = event.asEndElement(); + final QName originalName = e.getName(); + final QName name = convert(originalName); + final List<Namespace> namespaces = importNS(e.getNamespaces(), name != originalName); + if (namespaces != null) { + event = new FilteredEvent.End(e, name, namespaces); + } /* - * If this is an end element, close the last open one with a matching name. - * It should be the last list element in a well-formed XML, but we loop in - * the list anyway as a safety. + * Close the last start element with a matching name. It should be the last element + * on the list in a well-formed XML, but we loop in the list anyway as a safety. */ - final String name = getLocalName(); - for (int i = outerElements.size(); --i >= 0;) { - if (name.equals(outerElements.get(i))) { - outerElements.remove(i); - break; + if (next) { + for (int i = outerElements.size(); --i >= 0;) { + if (name.equals(outerElements.get(i))) { + outerElements.remove(i); + break; + } } } break; } } - return type; - } - - /** - * Returns the namespace of the given ISO type, or {@code null} if unknown. - * This is the namespace used in JAXB annotations. - * - * @param type a class name defined by ISO 19115 or related standards (e.g. {@code "CI_Citation"}. - * @return a namespace for the given type, or {@code null} if unknown. - */ - static String namespace(final String type) { - /* - * Same implementation than namespaceOf(type) but without DECLARING_TYPES.get(…) - * since that value should alway be null for class names. - */ - final Map<String,String> attributes = NAMESPACES.get(type); - return (attributes != null) ? attributes.get(TYPE_KEY) : null; + return event; } /** - * Return the namespace used by implementation (the SIS classes with JAXB annotations) - * in the context of the current part of the XML document being read. + * Imports a name read from the XML document to the name to give to JAXB. + * The new namespace depends on both the old namespace and the element name. + * The prefix is left unchanged since it can be arbitrary (even if confusing for + * human reader used to ISO/TC211 prefixes, it is non-ambiguous to the computer). * - * @param localPart the local name of the element or attribute currently being read. + * @param name the name of the element or attribute currently being read. * @return the namespace URI for the element or attribute in the current context (e.g. an ISO 19115-3 namespace), * or {@code null} if the given name is unknown. */ - private String namespaceOf(final String localPart) { - renamed = localPart; + private QName convert(QName name) { + String namespace = null; // In this method, null means no change to given name. + String localPart = name.getLocalPart(); final Set<String> declaringTypes = DECLARING_TYPES.get(localPart); if (declaringTypes == null) { /* @@ -302,7 +362,7 @@ final class FilteredReader extends Strea */ final Map<String,String> attributes = NAMESPACES.get(localPart); if (attributes != null) { - return attributes.get(TYPE_KEY); + namespace = attributes.get(TYPE_KEY); // May be null. } } else { /* @@ -310,105 +370,117 @@ final class FilteredReader extends Strea * possible parent element. Then, we use the namespace associated with that parent. */ for (int i = outerElements.size(); --i >= 0;) { - final String parent = outerElements.get(i); + final String parent = outerElements.get(i).getLocalPart(); if (declaringTypes.contains(parent)) { /* * A NullPointerException below would be a bug in our algorithm because * we constructed DECLARING_TYPES from NAMESPACES keys only. */ final Map<String,String> attributes = NAMESPACES.get(parent); - String uri = attributes.get(localPart); - if (!isNamespace(uri)) { - renamed = uri; - uri = attributes.get(uri); + namespace = attributes.get(localPart); + if (!isNamespace(namespace)) { + localPart = namespace; + namespace = attributes.get(namespace); } - return uri; + break; } } } - return null; - } - - /** - * Converts a name read from the XML document to the name to give to JAXB. - * The new namespace depends on both the old namespace and the element name. - */ - private QName importNS(QName name) { - final String namespaceURI = name.getNamespaceURI(); - final String localPart = name.getLocalPart(); - String replacement = namespaceOf(localPart); - if (replacement == null) { - replacement = version.importNS(namespaceURI); + /* + * If above code found no namespace by looking in our dictionary of special cases, + * maybe there is a simple one-to-one relationship between the old and new namespaces. + * Otherwise we leave the namespace unchanged. + */ + final String oldNS = name.getNamespaceURI(); + if (namespace == null) { + namespace = version.importNS(oldNS); } - if (!replacement.equals(namespaceURI) || !localPart.equals(renamed)) { - name = new QName(replacement, renamed, name.getPrefix()); + /* + * Build a new name if any component (URI or local part) changed. Do not specify the prefix + * because the same URI in the XML document could map to many different prefixes after we + * converted to the namespaces used by JAXB. Having the same prefix for those different URI + * may be a source of confusion. + */ + if (!namespace.equals(oldNS) || !localPart.equals(name.getLocalPart())) { + name = new QName(namespace, localPart); } return name; } - /** Replaces the given URI if needed, then forwards the call. */ - @Override - public void require(final int type, final String namespaceURI, final String localName) throws XMLStreamException { - super.require(type, version.exportNS(namespaceURI), localName); - } - - /** Returns the context of the underlying reader wrapped in a filter that converts the namespaces on the fly. */ - @Override - public NamespaceContext getNamespaceContext() { - return FilteredNamespaces.importNS(super.getNamespaceContext(), version); - } - - /** Forwards the call, then replaces the namespace URI if needed. */ - @Override - public QName getName() { - return importNS(super.getName()); - } - - /** Forwards the call, then replaces the namespace URI if needed. */ - @Override - public QName getAttributeName(final int index) { - return importNS(super.getAttributeName(index)); + /** + * Imports an attribute read from the XML document. + * If there is no name change, then this method returns the given instance as-is. + */ + private Attribute convert(Attribute attribute) { + final QName originalName = attribute.getName(); + final QName name = convert(originalName); + if (name != originalName) { + attribute = new FilteredEvent.Attr(attribute, name); + } + return attribute; } /** - * Returns the namespace of current element, after replacement by the URI used by SIS. - * This replacement depends on the current local name in addition of current namespace. + * Imports a namespace read from the XML document. This may imply a prefix change. + * If there is no namespace change, then this method returns the given instance as-is. + * + * @param namespace the namespace to import. */ - @Override - public String getNamespaceURI() { - String namespace = namespaceOf(getLocalName()); - if (namespace == null) { - namespace = version.importNS(super.getNamespaceURI()); + private Namespace importNS(final Namespace namespace) { + String uri = namespace.getNamespaceURI(); + if (uri != null && !uri.isEmpty()) { + uri = FilteredXML.removeTrailingSlash(uri); + final String imported = version.importNS(uri); + if (imported != uri) { + return new FilteredEvent.NS(namespace, Namespaces.getPreferredPrefix(imported, namespace.getPrefix()), imported); + } } return namespace; } /** - * Forwards the call, then replaces the returned URI if needed. + * Imports the namespaces read from the XML document. * - * <b>Note:</b> the index passed to this method is the index of a namespace declaration on the root element. - * This should not matter as long as each <em>element</em> has the proper namespace URI. - */ - @Override - public String getNamespaceURI(int index) { - return version.importNS(super.getNamespaceURI(index)); + * @param namespaces the namespaces to filter. + * @param changed whether to unconditionally pretend that there is a change. + * @return the updated namespaces, or {@code null} if there is no changes. + */ + private List<Namespace> importNS(final Iterator<Namespace> namespaces, boolean changed) { + if (!namespaces.hasNext()) { + return changed ? Collections.emptyList() : null; + } + final List<Namespace> modified = new ArrayList<>(); + do { + Namespace namespace = namespaces.next(); + changed |= (namespace != (namespace = importNS(namespace))); + modified.add(namespace); + } while (namespaces.hasNext()); + return changed ? modified : null; } - /** Forwards the call, then replaces the returned URI if needed. */ + /** + * Reads the content of a text-only element. Forwards from the underlying reader as-is. + */ @Override - public String getNamespaceURI(final String prefix) { - return version.importNS(super.getNamespaceURI(prefix)); + public String getElementText() throws XMLStreamException { + return in.getElementText(); } - /** Forwards the call, then replaces the returned URI if needed. */ + /** + * Get the value of a feature/property from the underlying implementation. + */ @Override - public String getAttributeNamespace(final int index) { - return version.importNS(super.getAttributeNamespace(index)); + public Object getProperty(final String name) { + return in.getProperty(name); } - /** Replaces the given URI if needed, then forwards the call. */ + /** + * Frees any resources associated with this reader. + * This method does not close the underlying input source. + */ @Override - public String getAttributeValue(final String namespaceUri, final String localName) { - return super.getAttributeValue(version.exportNS(namespaceUri), localName); + public void close() throws XMLStreamException { + outerElements.clear(); + in.close(); } } Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java?rev=1824166&r1=1824165&r2=1824166&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java [UTF-8] Tue Feb 13 18:02:12 2018 @@ -19,7 +19,6 @@ package org.apache.sis.xml; import java.util.Map; import java.util.LinkedHashMap; import java.util.List; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -51,18 +50,13 @@ import static javax.xml.stream.XMLStream * @since 1.0 * @module */ -final class FilteredWriter implements XMLEventWriter { +final class FilteredWriter extends FilteredXML implements XMLEventWriter { /** * Where events are sent. */ private final XMLEventWriter out; /** - * The other version to marshal to. - */ - private final FilterVersion version; - - /** * Keep track of namespace URIs that have already been declared so they don't get duplicated. * This map is recycled in two different contexts: * @@ -74,19 +68,12 @@ final class FilteredWriter implements XM private final Map<String, Namespace> uniqueNamespaces; /** - * Temporary list of attributes after their namespace change. - * This list is recycled for each XML element to be read. - */ - private final List<Attribute> exportedAttributes; - - /** * Creates a new filter for the given version of the standards. */ FilteredWriter(final XMLEventWriter out, final FilterVersion version) { + super(version); this.out = out; - this.version = version; uniqueNamespaces = new LinkedHashMap<>(); - exportedAttributes = new ArrayList<>(); } /** @@ -113,11 +100,7 @@ final class FilteredWriter implements XM private Namespace exportIfNew(final Namespace namespace) { String uri = namespace.getNamespaceURI(); if (uri != null && !uri.isEmpty()) { - final int end = uri.length() - 1; - if (uri.charAt(end) == '/') { - uri = uri.substring(0, end); // Trim trailing '/' in URI. - } - final String exported = version.exportNS(uri); + final String exported = version.exportNS(removeTrailingSlash(uri)); if (exported != uri) { return uniqueNamespaces.computeIfAbsent(exported, (k) -> { return new FilteredEvent.NS(namespace, Namespaces.getPreferredPrefix(k, namespace.getPrefix()), k); @@ -188,21 +171,13 @@ final class FilteredWriter implements XM final Attribute a = it.next(); final Attribute ae = export(a); changed |= (a != ae); - exportedAttributes.add(ae); + renamedAttributes.add(ae); } final List<Namespace> namespaces = export(e.getNamespaces(), changed); if (namespaces != null) { - final List<Attribute> attributes; - switch (exportedAttributes.size()) { - case 0: attributes = Collections.emptyList(); break; // Avoid object creation for this common case. - case 1: attributes = Collections.singletonList(exportedAttributes.remove(0)); break; - default: attributes = Arrays.asList(exportedAttributes.toArray(new Attribute[exportedAttributes.size()])); - exportedAttributes.clear(); - break; - } - event = new FilteredEvent.Start(e, name, namespaces, attributes, version); + event = new FilteredEvent.Start(e, name, namespaces, attributes(), version); } else { - exportedAttributes.clear(); + renamedAttributes.clear(); } break; } Added: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredXML.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredXML.java?rev=1824166&view=auto ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredXML.java (added) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredXML.java [UTF-8] Tue Feb 13 18:02:12 2018 @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.xml; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import javax.xml.stream.events.Attribute; + + +/** + * Base class of XML reader or writer replacing the namespaces used by JAXB by namespaces used in the XML document, + * or conversely (depending on the direction of the I/O operation). + * + * See {@link FilteredNamespaces} for more information. + * + * @author Martin Desruisseaux (Geomatys) + * @author Cullen Rombach (Image Matters) + * @version 1.0 + * @since 1.0 + * @module + */ +abstract class FilteredXML { + /** + * The external XML format version to (un)marshal from. + */ + final FilterVersion version; + + /** + * Temporary list of attributes after their namespace change. + * This list is recycled for each XML element to be read or written. + */ + final List<Attribute> renamedAttributes; + + /** + * Creates a new XML reader or writer. + */ + FilteredXML(final FilterVersion version) { + this.version = version; + renamedAttributes = new ArrayList<>(); + } + + /** + * Removes the trailing slash in given URI, if any. It is caller's responsibility + * to ensure that the URI is not null and not empty before to invoke this method. + */ + static String removeTrailingSlash(String uri) { + final int end = uri.length() - 1; + if (uri.charAt(end) == '/') { + uri = uri.substring(0, end); + } + return uri; + } + + /** + * Returns a snapshot of {@link #renamedAttributes} list and clears the later. + */ + final List<Attribute> attributes() { + final List<Attribute> attributes; + switch (renamedAttributes.size()) { + case 0: attributes = Collections.emptyList(); break; // Avoid object creation for this common case. + case 1: attributes = Collections.singletonList(renamedAttributes.remove(0)); break; + default: attributes = Arrays.asList(renamedAttributes.toArray(new Attribute[renamedAttributes.size()])); + renamedAttributes.clear(); + break; + } + return attributes; + } +} Propchange: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredXML.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredXML.java ------------------------------------------------------------------------------ svn:mime-type = text/plain;charset=UTF-8 Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/InputFactory.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/InputFactory.java?rev=1824166&r1=1824165&r2=1824166&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/InputFactory.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/InputFactory.java [UTF-8] Tue Feb 13 18:02:12 2018 @@ -37,7 +37,7 @@ import org.apache.sis.util.Static; * only when first needed, when initializing this class. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.0 * @since 0.4 * @module */ @@ -55,7 +55,7 @@ final class InputFactory extends Static /* * Do not provide convenience method for java.io.File, because the caller needs to close the created - * input stream himself (this is not done by XMLStreamReader.close(), despite its method name). + * input stream himself (this is not done by XMLEventReader.close(), despite its method name). */ /** @@ -65,8 +65,8 @@ final class InputFactory extends Static * @return the reader. * @throws XMLStreamException if the reader can not be created. */ - public static XMLStreamReader createXMLStreamReader(final InputStream in) throws XMLStreamException { - return FACTORY.createXMLStreamReader(in); + public static XMLEventReader createXMLEventReader(final InputStream in) throws XMLStreamException { + return FACTORY.createXMLEventReader(in); } /** @@ -76,8 +76,8 @@ final class InputFactory extends Static * @return the reader. * @throws XMLStreamException if the reader can not be created. */ - public static XMLStreamReader createXMLStreamReader(final Reader in) throws XMLStreamException { - return FACTORY.createXMLStreamReader(in); + public static XMLEventReader createXMLEventReader(final Reader in) throws XMLStreamException { + return FACTORY.createXMLEventReader(in); } /** @@ -87,8 +87,8 @@ final class InputFactory extends Static * @return the reader. * @throws XMLStreamException if the reader can not be created. */ - public static XMLStreamReader createXMLStreamReader(final InputSource in) throws XMLStreamException { - return FACTORY.createXMLStreamReader(new SAXSource(in)); + public static XMLEventReader createXMLEventReader(final InputSource in) throws XMLStreamException { + return FACTORY.createXMLEventReader(new SAXSource(in)); } /** @@ -98,8 +98,8 @@ final class InputFactory extends Static * @return the reader. * @throws XMLStreamException if the reader can not be created. */ - public static XMLStreamReader createXMLStreamReader(final XMLEventReader in) throws XMLStreamException { - return FACTORY.createXMLStreamReader(new StAXSource(in)); + public static XMLEventReader createXMLEventReader(final XMLStreamReader in) throws XMLStreamException { + return FACTORY.createXMLEventReader(new StAXSource(in)); } /** @@ -109,8 +109,8 @@ final class InputFactory extends Static * @return the reader. * @throws XMLStreamException if the reader can not be created. */ - public static XMLStreamReader createXMLStreamReader(final Node in) throws XMLStreamException { - return FACTORY.createXMLStreamReader(new DOMSource(in)); + public static XMLEventReader createXMLEventReader(final Node in) throws XMLStreamException { + return FACTORY.createXMLEventReader(new DOMSource(in)); } /** @@ -120,7 +120,7 @@ final class InputFactory extends Static * @return the reader. * @throws XMLStreamException if the reader can not be created. */ - public static XMLStreamReader createXMLStreamReader(final Source in) throws XMLStreamException { - return FACTORY.createXMLStreamReader(in); + public static XMLEventReader createXMLEventReader(final Source in) throws XMLStreamException { + return FACTORY.createXMLEventReader(in); } } Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledUnmarshaller.java URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledUnmarshaller.java?rev=1824166&r1=1824165&r2=1824166&view=diff ============================================================================== --- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledUnmarshaller.java [UTF-8] (original) +++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledUnmarshaller.java [UTF-8] Tue Feb 13 18:02:12 2018 @@ -55,7 +55,7 @@ import org.apache.sis.internal.jaxb.Cont * </ul> * * @author Martin Desruisseaux (Geomatys) - * @version 0.4 + * @version 1.0 * @since 0.3 * @module */ @@ -117,7 +117,7 @@ final class PooledUnmarshaller extends P * @param version identify the namespace substitutions to perform. * @return the unmarshalled object. */ - private Object unmarshal(XMLStreamReader input, final FilterVersion version) + private Object unmarshal(XMLEventReader input, final FilterVersion version) throws XMLStreamException, JAXBException { input = new FilteredReader(input, version); @@ -133,10 +133,10 @@ final class PooledUnmarshaller extends P } /** - * Same as {@link #unmarshal(XMLStreamReader, FilterVersion)}, but delegating to the unmarshaller + * Same as {@link #unmarshal(XMLEventReader, FilterVersion)}, but delegating to the unmarshaller * methods returning a JAXB element instead than the one returning the object. */ - private <T> JAXBElement<T> unmarshal(XMLStreamReader input, final FilterVersion version, final Class<T> declaredType) + private <T> JAXBElement<T> unmarshal(XMLEventReader input, final FilterVersion version, final Class<T> declaredType) throws XMLStreamException, JAXBException { input = new FilteredReader(input, version); @@ -158,7 +158,7 @@ final class PooledUnmarshaller extends P public Object unmarshal(final InputStream input) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version); + return unmarshal(InputFactory.createXMLEventReader(input), version); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -179,7 +179,7 @@ final class PooledUnmarshaller extends P final FilterVersion version = getFilterVersion(); if (version != null) try { try (InputStream s = input.openStream()) { - return unmarshal(InputFactory.createXMLStreamReader(s), version); + return unmarshal(InputFactory.createXMLEventReader(s), version); } } catch (IOException | XMLStreamException e) { throw new JAXBException(e); @@ -201,7 +201,7 @@ final class PooledUnmarshaller extends P final FilterVersion version = getFilterVersion(); if (version != null) try { try (InputStream s = new BufferedInputStream(new FileInputStream(input))) { - return unmarshal(InputFactory.createXMLStreamReader(s), version); + return unmarshal(InputFactory.createXMLEventReader(s), version); } } catch (IOException | XMLStreamException e) { throw new JAXBException(e); @@ -222,7 +222,7 @@ final class PooledUnmarshaller extends P public Object unmarshal(final Reader input) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version); + return unmarshal(InputFactory.createXMLEventReader(input), version); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -242,7 +242,7 @@ final class PooledUnmarshaller extends P public Object unmarshal(final InputSource input) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version); + return unmarshal(InputFactory.createXMLEventReader(input), version); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -262,7 +262,7 @@ final class PooledUnmarshaller extends P public Object unmarshal(final Node input) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version); + return unmarshal(InputFactory.createXMLEventReader(input), version); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -282,7 +282,7 @@ final class PooledUnmarshaller extends P public <T> JAXBElement<T> unmarshal(final Node input, final Class<T> declaredType) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version, declaredType); + return unmarshal(InputFactory.createXMLEventReader(input), version, declaredType); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -302,7 +302,7 @@ final class PooledUnmarshaller extends P public Object unmarshal(final Source input) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version); + return unmarshal(InputFactory.createXMLEventReader(input), version); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -322,7 +322,7 @@ final class PooledUnmarshaller extends P public <T> JAXBElement<T> unmarshal(final Source input, final Class<T> declaredType) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version, declaredType); + return unmarshal(InputFactory.createXMLEventReader(input), version, declaredType); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -339,44 +339,10 @@ final class PooledUnmarshaller extends P * Delegates the unmarshalling to the wrapped unmarshaller. */ @Override - public Object unmarshal(XMLStreamReader input) throws JAXBException { - final FilterVersion version = getFilterVersion(); - if (version != null) { - input = new FilteredReader(input, version); - } - final Context context = begin(); - try { - return unmarshaller.unmarshal(input); - } finally { - context.finish(); - } - } - - /** - * Delegates the unmarshalling to the wrapped unmarshaller. - */ - @Override - public <T> JAXBElement<T> unmarshal(XMLStreamReader input, final Class<T> declaredType) throws JAXBException { - final FilterVersion version = getFilterVersion(); - if (version != null) { - input = new FilteredReader(input, version); - } - final Context context = begin(); - try { - return unmarshaller.unmarshal(input, declaredType); - } finally { - context.finish(); - } - } - - /** - * Delegates the unmarshalling to the wrapped unmarshaller. - */ - @Override - public Object unmarshal(final XMLEventReader input) throws JAXBException { + public Object unmarshal(final XMLStreamReader input) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version); + return unmarshal(InputFactory.createXMLEventReader(input), version); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -393,10 +359,10 @@ final class PooledUnmarshaller extends P * Delegates the unmarshalling to the wrapped unmarshaller. */ @Override - public <T> JAXBElement<T> unmarshal(final XMLEventReader input, final Class<T> declaredType) throws JAXBException { + public <T> JAXBElement<T> unmarshal(final XMLStreamReader input, final Class<T> declaredType) throws JAXBException { final FilterVersion version = getFilterVersion(); if (version != null) try { - return unmarshal(InputFactory.createXMLStreamReader(input), version, declaredType); + return unmarshal(InputFactory.createXMLEventReader(input), version, declaredType); } catch (XMLStreamException e) { throw new JAXBException(e); } else { @@ -409,6 +375,40 @@ final class PooledUnmarshaller extends P } } + /** + * Delegates the unmarshalling to the wrapped unmarshaller. + */ + @Override + public Object unmarshal(XMLEventReader input) throws JAXBException { + final FilterVersion version = getFilterVersion(); + if (version != null) { + input = new FilteredReader(input, version); + } + final Context context = begin(); + try { + return unmarshaller.unmarshal(input); + } finally { + context.finish(); + } + } + + /** + * Delegates the unmarshalling to the wrapped unmarshaller. + */ + @Override + public <T> JAXBElement<T> unmarshal(XMLEventReader input, final Class<T> declaredType) throws JAXBException { + final FilterVersion version = getFilterVersion(); + if (version != null) { + input = new FilteredReader(input, version); + } + final Context context = begin(); + try { + return unmarshaller.unmarshal(input, declaredType); + } finally { + context.finish(); + } + } + /** * Delegates to the wrapped unmarshaller. */