sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1823504 - /sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/
Date Wed, 07 Feb 2018 18:01:51 GMT
Author: desruisseaux
Date: Wed Feb  7 18:01:51 2018
New Revision: 1823504

URL: http://svn.apache.org/viewvc?rev=1823504&view=rev
Log:
Replace the use of XMLStreamWriter by XMLEventWriter when writing legacy ISO 19139 metadata.
The intent is to make easier to handle renaming of metadata element or their displacement.

Added:
    sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java
  (with props)
    sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java
      - copied, changed from r1823503, sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamWriter.java
Removed:
    sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamWriter.java
Modified:
    sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java
    sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamResolver.java
    sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/OutputFactory.java
    sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java
    sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java

Added: 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=1823504&view=auto
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java
(added)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java
[UTF-8] Wed Feb  7 18:01:51 2018
@@ -0,0 +1,275 @@
+/*
+ * 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.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Iterator;
+import javax.xml.XMLConstants;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.Namespace;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+
+/**
+ * Base class of events that are wrappers over the events emitted during the reading or writing
of an XML document.
+ * Those wrappers are used for changing the namespace and sometime the name of XML elements
or attributes.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+abstract class FilteredEvent<E extends XMLEvent> implements XMLEvent {
+    /**
+     * The event to be exported in a different namespace.
+     */
+    final E event;
+
+    /**
+     * Exported name of the attribute or element. Will often (but not necessarily) have
+     * the same local part than {@code event.getName()} but a different namespace.
+     */
+    final QName name;
+
+    /**
+     * Exports a new event.
+     *
+     * @param  event  the event to be exported in a different namespace.
+     * @param  name   the exported name of the attribute or element.
+     */
+    FilteredEvent(final E event, final QName name) {
+        this.event  = event;
+        this.name   = name;
+    }
+
+    @Override public boolean      isStartElement()          {return false;}
+    @Override public boolean      isAttribute()             {return false;}
+    @Override public boolean      isNamespace()             {return false;}
+    @Override public boolean      isEndElement()            {return false;}
+    @Override public boolean      isEntityReference()       {return false;}
+    @Override public boolean      isProcessingInstruction() {return false;}
+    @Override public boolean      isCharacters()            {return false;}
+    @Override public boolean      isStartDocument()         {return false;}
+    @Override public boolean      isEndDocument()           {return false;}
+    @Override public StartElement asStartElement()          {throw new ClassCastException();}
+    @Override public EndElement   asEndElement()            {throw new ClassCastException();}
+    @Override public Characters   asCharacters()            {throw new ClassCastException();}
+    @Override public Location     getLocation()             {return event.getLocation();}
+    @Override public QName        getSchemaType()           {return event.getSchemaType();}
+    final     public QName        getName()                 {return name;}
+
+    /**
+     * Append the name to the given output.
+     * This is a convenience method for {@link #write(Appendable)} implementations.
+     */
+    final Appendable name(final Appendable out) throws IOException {
+        final String prefix = name.getPrefix();
+        if (prefix != null && !prefix.isEmpty()) {
+            out.append(prefix).append(':');
+        }
+        return out.append(name.getLocalPart());
+    }
+
+    /**
+     * Implementation of {@link #writeAsEncodedUnicode(Writer)} and {@link #toString()}.
+     */
+    abstract void write(Appendable out) throws IOException;
+
+    /**
+     * Writes the event as per the XML 1.0 without indentation or whitespace.
+     * This implementation delegates to {@link #write(Appendable)}.
+     */
+    @Override
+    public final void writeAsEncodedUnicode(final Writer writer) throws XMLStreamException
{
+        try {
+            write(writer);
+        } catch (IOException e) {
+            throw new XMLStreamException(e);
+        }
+    }
+
+    /**
+     * Returns the event as per the XML 1.0 without indentation or whitespace.
+     * This implementation delegates to {@link #write(Appendable)}.
+     */
+    @Override
+    public final String toString() {
+        final StringBuilder out = new StringBuilder();
+        try {
+            write(out);
+        } catch (IOException e) {       // Should never happen since we write to a StringBuilder.
+            return e.toString();
+        }
+        return out.toString();
+    }
+
+    /**
+     * Wrapper over a namespace emitted during the reading or writing of an XML document.
+     * This wrapper is used for changing the namespace URI.
+     */
+    static final class NS extends FilteredEvent<Namespace> implements Namespace {
+        /** The URI of the namespace. */
+        private final String namespaceURI;
+
+        /** Wraps the given event with a different prefix and URI. */
+        NS(final Namespace event, final String prefix, final String namespaceURI) {
+            super(event, new QName(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, prefix, XMLConstants.XMLNS_ATTRIBUTE));
+            this.namespaceURI = namespaceURI;
+        }
+
+        @Override public boolean   isNamespace()                   {return true;}
+        @Override public int       getEventType()                  {return NAMESPACE;}
+        @Override public String    getNamespaceURI()               {return namespaceURI;}
+        @Override public String    getValue()                      {return namespaceURI;}
+        @Override public String    getDTDType()                    {return event.getDTDType();}
+        @Override public boolean   isSpecified()                   {return event.isSpecified();}
+        @Override public String    getPrefix()                     {return (name != null)
?  name.getLocalPart() : null;}
+        @Override public boolean   isDefaultNamespaceDeclaration() {return (name != null)
&& name.getLocalPart().isEmpty();}
+        @Override void write(final Appendable out) throws IOException {
+            name(out).append("=\"").append(namespaceURI).append('"');
+        }
+    }
+
+    /**
+     * Wrapper over an attribute emitted during the reading or writing of an XML document.
+     * This wrapper is used for changing the namespace of the attribute.
+     */
+    static final class Attr extends FilteredEvent<Attribute> implements Attribute {
+        /** Wraps the given event with a different name. */
+        Attr(final Attribute event, final QName name) {
+            super(event, name);
+        }
+
+        /** Cast or wrap the given attribute to an {@code Attr} instance. */
+        static Attr castOrWrap(final Attribute a) {
+            if (a instanceof Attr) return (Attr) a;
+            else return new Attr(a, a.getName());
+        }
+
+        @Override public boolean   isAttribute()     {return true;}
+        @Override public int       getEventType()    {return ATTRIBUTE;}
+        @Override public String    getValue()        {return event.getValue();}
+        @Override public String    getDTDType()      {return event.getDTDType();}
+        @Override public boolean   isSpecified()     {return event.isSpecified();}
+        @Override void write(final Appendable out) throws IOException {
+            name(out).append("=\"").append(event.getValue()).append('"');
+        }
+    }
+
+    /**
+     * Wrapper over an element emitted during the reading or writing of an XML document.
+     * This wrapper is used for changing the namespace and sometime the name of the element.
+     */
+    static final class End extends FilteredEvent<EndElement> implements EndElement
{
+        /** The namespaces, may or may not be the same than the wrapped event. */
+        private final List<Namespace> namespaces;
+
+        /** Wraps the given event with potentially different name and namespaces. */
+        End(final EndElement event, final QName name, final List<Namespace> namespaces)
{
+            super(event, name);
+            this.namespaces = namespaces;
+        }
+
+        @Override public boolean             isEndElement()  {return true;}
+        @Override public EndElement          asEndElement()  {return this;}
+        @Override public int                 getEventType()  {return END_ELEMENT;}
+        @Override public Iterator<Namespace> getNamespaces() {return namespaces.iterator();}
+        @Override void write(final Appendable out) throws IOException {
+            name(out.append("</")).append('>');
+        }
+    }
+
+    /**
+     * Wrapper over an element emitted during the reading or writing of an XML document.
+     * 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
{
+        /** The namespaces, may or may not be the same than the wrapped event. */
+        private final List<Namespace> namespaces;
+
+        /** The attributes, may or may not be the same than the wrapped event. */
+        private final List<Attribute> attributes;
+
+        /** The version to export, used for wrapping namespace context. */
+        private 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) {
+            super(event, name);
+            this.namespaces = namespaces;
+            this.attributes = attributes;
+            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();}
+
+        /**
+         * 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) {
+            for (final Attribute attr : attributes) {
+                if (name.equals(attr.getName())) {
+                    return attr;
+                }
+            }
+            return null;
+        }
+
+        /** Gets a read-only namespace context. */
+        @Override public NamespaceContext getNamespaceContext() {
+            final NamespaceContext context = event.getNamespaceContext();
+            return (context != null) ? new FilteredNamespaces(context, version, false) :
null;
+        }
+
+        /** Gets the value that the prefix is bound to in the context of this element. */
+        @Override public String getNamespaceURI(final String prefix) {
+            final NamespaceContext context = event.getNamespaceContext();
+            if (context != null) {
+                final String uri = context.getNamespaceURI(prefix);
+                return version.toView.getOrDefault(uri, uri);
+            }
+            return null;
+        }
+
+        /** Writes the event as per the XML 1.0 without indentation or whitespace. */
+        @Override void write(final Appendable out) throws IOException {
+            name(out.append('<'));
+            final int n = attributes.size();
+            for (int i=0; i<n; i++) {
+                if (i != 0) out.append(' ');
+                Attr.castOrWrap(attributes.get(i)).write(out);
+            }
+            out.append('>');
+        }
+    }
+}

Propchange: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredEvent.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/FilteredNamespaces.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java?rev=1823504&r1=1823503&r2=1823504&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java
[UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredNamespaces.java
[UTF-8] Wed Feb  7 18:01:51 2018
@@ -23,7 +23,7 @@ import javax.xml.namespace.NamespaceCont
 
 /**
  * Substitutes at (un)marshalling time the XML namespaces used by SIS by the namespaces used
in the XML document.
- * This class is used internally by {@link FilteredStreamReader} and {@link FilteredStreamWriter}
only.
+ * This class is used internally by {@link FilteredStreamReader} and {@link FilteredWriter}
only.
  * Current {@code FilteredNamespaces} implementation takes care of XML prefixes only;
  * the stream reader and writer do the rest of the work.
  *
@@ -63,7 +63,7 @@ import javax.xml.namespace.NamespaceCont
  */
 final class FilteredNamespaces implements NamespaceContext {
     /**
-     * The context to wrap, given by {@link FilteredStreamReader} or {@link FilteredStreamWriter}.
+     * The context to wrap, given by {@link FilteredStreamReader} or {@link FilteredWriter}.
      *
      * @see javax.xml.stream.XMLStreamReader#getNamespaceContext()
      * @see javax.xml.stream.XMLStreamWriter#getNamespaceContext()
@@ -131,7 +131,7 @@ final class FilteredNamespaces implement
      * Returns all prefixes for the given namespace.
      */
     @Override
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings("unchecked")          // TODO: remove on JDK9
     public Iterator<String> getPrefixes(final String namespaceURI) {
         return context.getPrefixes(toImpl.getOrDefault(namespaceURI, namespaceURI));
     }

Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamResolver.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamResolver.java?rev=1823504&r1=1823503&r2=1823504&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamResolver.java
[UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamResolver.java
[UTF-8] Wed Feb  7 18:01:51 2018
@@ -87,7 +87,7 @@ final class FilteredStreamResolver exten
     private static final Map<String, Map<String,String>> NAMESPACES;
     static {
         final Map<String, Map<String,String>> m = new HashMap<>(250);
-        try (final LineNumberReader in = new LineNumberReader(new InputStreamReader(
+        try (LineNumberReader in = new LineNumberReader(new InputStreamReader(
                 FilteredStreamResolver.class.getResourceAsStream(FILENAME), "UTF-8")))
         {
             Map<String,String> attributes = null;   // All attributes for a given type.

Copied: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java
(from r1823503, sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamWriter.java)
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java?p2=sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java&p1=sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamWriter.java&r1=1823503&r2=1823504&rev=1823504&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredStreamWriter.java
[UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java
[UTF-8] Wed Feb  7 18:01:51 2018
@@ -16,17 +16,30 @@
  */
 package org.apache.sis.xml;
 
-import java.util.Set;
-import java.util.HashSet;
-import javax.xml.stream.XMLStreamWriter;
+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;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
 import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.Namespace;
 import javax.xml.namespace.NamespaceContext;
-import org.apache.sis.internal.util.StreamWriterDelegate;
+import javax.xml.namespace.QName;
+
+import static javax.xml.stream.XMLStreamConstants.*;
 
 
 /**
  * A filter replacing the namespaces used by JAXB by other namespaces to be used in the XML
document
- * at marshalling time. This class forwards every method calls to the wrapped {@link XMLStreamWriter},
+ * at marshalling time. This class forwards every method calls to the wrapped {@link XMLEventWriter},
  * with all {@code namespaceURI} arguments filtered before to be delegated.
  *
  * See {@link FilteredNamespaces} for more information.
@@ -34,10 +47,15 @@ import org.apache.sis.internal.util.Stre
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Cullen Rombach (Image Matters)
  * @version 1.0
- * @since   0.4
+ * @since   1.0
  * @module
  */
-final class FilteredStreamWriter extends StreamWriterDelegate {
+final class FilteredWriter implements XMLEventWriter {
+    /**
+     * Where events are sent.
+     */
+    private final XMLEventWriter out;
+
     /**
      * The other version to marshal to.
      */
@@ -45,110 +63,226 @@ final class FilteredStreamWriter extends
 
     /**
      * Keep track of namespace URIs that have already been declared so they don't get duplicated.
+     * This map is recycled in two different contexts:
+     *
+     * <ul>
+     *   <li>In a sequence of {@code NAMESPACE} events.</li>
+     *   <li>In the namespaces of a start element.</li>
+     * </ul>
+     */
+    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 Set<String> writtenNamespaceURIs;
+    private final List<Attribute> exportedAttributes;
 
     /**
      * Creates a new filter for the given version of the standards.
      */
-    FilteredStreamWriter(final XMLStreamWriter out, final FilterVersion version) {
-        super(out);
+    FilteredWriter(final XMLEventWriter out, final FilterVersion version) {
+        this.out = out;
         this.version = version;
-        writtenNamespaceURIs = new HashSet<>();
+        uniqueNamespaces = new LinkedHashMap<>();
+        exportedAttributes = new ArrayList<>();
     }
 
     /**
      * Returns the URI to write in the XML document.
+     * If there is no namespace change, then this method returns the given instance as-is.
      */
-    private String toView(final String uri) {
+    private String export(final String uri) {
         return version.toView.getOrDefault(uri, uri);
     }
 
-    /** Replaces the given URI if needed, then forwards the call. */
-    @Override
-    public void writeStartElement(final String namespaceURI, final String localName) throws
XMLStreamException {
-        out.writeStartElement(toView(namespaceURI), localName);
-    }
-
-    /** Replaces the given URI if needed, then forwards the call. */
-    @Override
-    public void writeStartElement(final String prefix, final String localName, final String
namespaceURI)
-            throws XMLStreamException
-    {
-        final String view = toView(namespaceURI);
-        out.writeStartElement(Namespaces.getPreferredPrefix(view, prefix), localName, view);
-    }
-
-    /** Replaces the given URI if needed, then forwards the call. */
-    @Override
-    public void writeEmptyElement(final String namespaceURI, final String localName) throws
XMLStreamException {
-        out.writeEmptyElement(toView(namespaceURI), localName);
+    /**
+     * Returns the name (prefix, namespace and local part) to write in the XML document.
+     * If there is no name change, then this method returns the given instance as-is.
+     */
+    private QName export(QName name) {
+        String uri = name.getNamespaceURI();
+        if (uri != null && !uri.isEmpty()) {                                // Optimization
for a common case.
+            uri = version.toView.get(uri);
+            if (uri != null) {
+                name = new QName(uri, name.getLocalPart(), Namespaces.getPreferredPrefix(uri,
name.getPrefix()));
+            }
+        }
+        return name;
     }
 
-    /** Replaces the given URI if needed, then forwards the call. */
-    @Override
-    public void writeEmptyElement(final String prefix, final String localName, final String
namespaceURI)
-            throws XMLStreamException
-    {
-        final String view = toView(namespaceURI);
-        out.writeEmptyElement(Namespaces.getPreferredPrefix(view, prefix), localName, view);
+    /**
+     * Returns the attribute to write in the XML document.
+     * If there is no name change, then this method returns the given instance as-is.
+     */
+    private Attribute export(Attribute attribute) {
+        final QName originalName = attribute.getName();
+        final QName name = export(originalName);
+        if (name != originalName) {
+            attribute = new FilteredEvent.Attr(attribute, name);
+        }
+        return attribute;
     }
 
-    /** Replaces the given URI if needed, then forwards the call. */
-    @Override
-    public void writeAttribute(final String prefix, final String namespaceURI, final String
localName,
-            final String value) throws XMLStreamException
-    {
-        final String view = toView(namespaceURI);
-        out.writeAttribute(Namespaces.getPreferredPrefix(view, prefix), view, localName,
value);
+    /**
+     * Returns the namespace to write in the XML document. This may imply a prefix change.
+     * If there is no namespace change, then this method returns the given instance as-is.
+     * To test if the returned namespace is a new one, callers should check if the size of
+     * {@link #uniqueNamespaces} changed.
+     *
+     * @param  namespace  the namespace to export.
+     */
+    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.toView.get(uri);
+            if (exported != null) {
+                return uniqueNamespaces.computeIfAbsent(exported, (k) -> {
+                    return new FilteredEvent.NS(namespace, Namespaces.getPreferredPrefix(k,
namespace.getPrefix()), k);
+                });
+            }
+            final Namespace c = uniqueNamespaces.put(uri, namespace);   // No namespace change
needed. Overwrite wrapper if any.
+            if (c != null) return c;
+        }
+        return namespace;
     }
 
-    /** Replaces the given URI if needed, then forwards the call. */
-    @Override
-    public void writeAttribute(final String namespaceURI, final String localName, final String
value)
-            throws XMLStreamException
-    {
-        out.writeAttribute(toView(namespaceURI), localName, value);
+    /**
+     * Returns the namespaces to write in the XML document, or {@code null} if there is no
change.
+     * If non-null, the result may contain less namespaces because duplicated entries are
omitted
+     * (duplication may occur as a result of replacing various ISO 19115-3 namespaces by
the legacy
+     * ISO 19139:2007 {@code "gmd"} unique namespace).
+     *
+     * @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> export(final Iterator<Namespace> namespaces, boolean
changed) {
+        if (!namespaces.hasNext()) {
+            return changed ? Collections.emptyList() : null;
+        }
+        do {
+            final Namespace namespace = namespaces.next();
+            changed |= (namespace != exportIfNew(namespace));
+        } while (namespaces.hasNext());
+        if (changed) {
+            assert !uniqueNamespaces.isEmpty();
+            final Namespace[] exported = uniqueNamespaces.values().toArray(new Namespace[uniqueNamespaces.size()]);
+            uniqueNamespaces.clear();
+            return Arrays.asList(exported);
+        } else {
+            uniqueNamespaces.clear();
+            return null;
+        }
     }
 
     /**
-     * Replaces the given URI if needed, then forwards the call.
-     * This method does nothing if the view of given URI has already be written.
-     * This filtering is applied because different URIs may be replaced by the same view.
+     * Adds an event to the output stream. This method may wrap the given event into another
event
+     * for changing the namespace and/or the prefix.
      */
     @Override
-    public void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException
{
-        final String view = toView(namespaceURI);
-        if (writtenNamespaceURIs.add(view)) {
-            out.writeNamespace(Namespaces.getPreferredPrefix(view, prefix), view);
+    @SuppressWarnings("unchecked")      // TODO: remove on JDK9
+    public void add(XMLEvent event) throws XMLStreamException {
+        switch (event.getEventType()) {
+            case ATTRIBUTE: {
+                event = export((Attribute) event);
+                break;
+            }
+            case NAMESPACE: {
+                final int n = uniqueNamespaces.size();
+                event = exportIfNew((Namespace) event);
+                if (uniqueNamespaces.size() == n) {
+                    return;                                     // An event has already been
created for that namespace.
+                }
+                break;
+            }
+            case START_ELEMENT: {
+                uniqueNamespaces.clear();                       // Discard entries created
by NAMESPACE events.
+                final StartElement e = event.asStartElement();
+                final QName originalName = e.getName();
+                final QName name = export(originalName);
+                boolean changed = name != originalName;
+                for (final Iterator<Attribute> it = e.getAttributes(); it.hasNext();)
{
+                    final Attribute a = it.next();
+                    final Attribute ae = export(a);
+                    changed |= (a != ae);
+                    exportedAttributes.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);
+                } else {
+                    exportedAttributes.clear();
+                }
+                break;
+            }
+            case END_ELEMENT: {
+                final EndElement e = event.asEndElement();
+                final QName originalName = e.getName();
+                final QName name = export(originalName);
+                final List<Namespace> namespaces = export(e.getNamespaces(), name !=
originalName);
+                if (namespaces != null) {
+                    event = new FilteredEvent.End(e, name, namespaces);
+                }
+                break;
+            }
         }
+        out.add(event);
     }
 
-    /** Replaces the given URI if needed, then forwards the call. */
+    /**
+     * Adds an entire stream to an output stream.
+     */
     @Override
-    public void writeDefaultNamespace(final String namespaceURI) throws XMLStreamException
{
-        out.writeDefaultNamespace(toView(namespaceURI));
+    public void add(final XMLEventReader reader) throws XMLStreamException {
+        while (reader.hasNext()) {
+            add(reader.nextEvent());
+        }
     }
 
-    /** Replaces the given URI if needed, then forwards the call. */
+    /**
+     * Gets the prefix the URI is bound to.
+     * This method replaces the given URI if needed, then forwards the call.
+     */
     @Override
     public String getPrefix(final String uri) throws XMLStreamException {
-        return out.getPrefix(toView(uri));
+        return out.getPrefix(export(uri));
     }
 
-    /** Replaces the given URI if needed, then forwards the call. */
+    /**
+     * Sets the prefix the URI is bound to.
+     * This method replaces the given URI if needed, then forwards the call.
+     */
     @Override
     public void setPrefix(final String prefix, final String uri) throws XMLStreamException
{
-        out.setPrefix(prefix, toView(uri));
+        out.setPrefix(prefix, export(uri));
     }
 
-    /** Replaces the given URI if needed, then forwards the call. */
+    /**
+     * Binds a URI to the default namespace.
+     * Replaces the given URI if needed, then forwards the call.
+     */
     @Override
     public void setDefaultNamespace(final String uri) throws XMLStreamException {
-        out.setDefaultNamespace(toView(uri));
+        out.setDefaultNamespace(export(uri));
     }
 
-    /** Unwraps the original context and forwards the call. */
+    /**
+     * Sets the current namespace context for prefix and URI bindings.
+     * Unwraps the original context and forwards the call.
+     */
     @Override
     public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
         if (context instanceof FilteredNamespaces) {
@@ -159,9 +293,28 @@ final class FilteredStreamWriter extends
         out.setNamespaceContext(context);
     }
 
-    /** Returns the context of the underlying writer wrapped in a filter that convert the
namespaces on the fly. */
+    /**
+     * Returns the context of the underlying writer wrapped in a filter that convert the
namespaces on the fly.
+     */
     @Override
     public NamespaceContext getNamespaceContext() {
         return new FilteredNamespaces(out.getNamespaceContext(), version, false);
     }
+
+    /**
+     * Writes any cached events to the underlying output mechanism.
+     */
+    @Override
+    public void flush() throws XMLStreamException {
+        out.flush();
+    }
+
+    /**
+     * Frees any resources associated with this writer.
+     */
+    @Override
+    public void close() throws XMLStreamException {
+        uniqueNamespaces.clear();
+        out.close();
+    }
 }

Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/OutputFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/OutputFactory.java?rev=1823504&r1=1823503&r2=1823504&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/OutputFactory.java
[UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/OutputFactory.java
[UTF-8] Wed Feb  7 18:01:51 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 OutputFactory extends Static
 
     /*
      * Do not provide convenience method for java.io.File, because the caller needs to close
the created
-     * output stream himself (this is not done by XMLStreamWriter.close(), despite its method
name).
+     * output stream himself (this is not done by XMLEventWriter.close(), despite its method
name).
      */
 
     /**
@@ -66,8 +66,8 @@ final class OutputFactory extends Static
      * @return the writer.
      * @throws XMLStreamException if the writer can not be created.
      */
-    public static XMLStreamWriter createXMLStreamWriter(OutputStream out, String encoding)
throws XMLStreamException {
-        return FACTORY.createXMLStreamWriter(out, encoding);
+    public static XMLEventWriter createXMLEventWriter(OutputStream out, String encoding)
throws XMLStreamException {
+        return FACTORY.createXMLEventWriter(out, encoding);
     }
 
     /**
@@ -77,8 +77,8 @@ final class OutputFactory extends Static
      * @return the writer.
      * @throws XMLStreamException if the writer can not be created.
      */
-    public static XMLStreamWriter createXMLStreamWriter(final Writer out) throws XMLStreamException
{
-        return FACTORY.createXMLStreamWriter(out);
+    public static XMLEventWriter createXMLEventWriter(final Writer out) throws XMLStreamException
{
+        return FACTORY.createXMLEventWriter(out);
     }
 
     /**
@@ -89,8 +89,8 @@ final class OutputFactory extends Static
      * @return the writer.
      * @throws XMLStreamException if the writer can not be created.
      */
-    public static XMLStreamWriter createXMLStreamWriter(final ContentHandler out) throws
XMLStreamException {
-        return FACTORY.createXMLStreamWriter(new SAXResult(out));
+    public static XMLEventWriter createXMLEventWriter(final ContentHandler out) throws XMLStreamException
{
+        return FACTORY.createXMLEventWriter(new SAXResult(out));
     }
 
     /**
@@ -101,8 +101,8 @@ final class OutputFactory extends Static
      * @return the writer.
      * @throws XMLStreamException if the writer can not be created.
      */
-    public static XMLStreamWriter createXMLStreamWriter(final Node out) throws XMLStreamException
{
-        return FACTORY.createXMLStreamWriter(new DOMResult(out));
+    public static XMLEventWriter createXMLEventWriter(final Node out) throws XMLStreamException
{
+        return FACTORY.createXMLEventWriter(new DOMResult(out));
     }
 
     /**
@@ -113,8 +113,8 @@ final class OutputFactory extends Static
      * @return the writer.
      * @throws XMLStreamException if the writer can not be created.
      */
-    public static XMLStreamWriter createXMLStreamWriter(final XMLEventWriter out) throws
XMLStreamException {
-        return FACTORY.createXMLStreamWriter(new StAXResult(out));
+    public static XMLEventWriter createXMLEventWriter(final XMLStreamWriter out) throws XMLStreamException
{
+        return FACTORY.createXMLEventWriter(new StAXResult(out));
     }
 
     /**
@@ -125,7 +125,7 @@ final class OutputFactory extends Static
      * @return the writer.
      * @throws XMLStreamException if the writer can not be created.
      */
-    public static XMLStreamWriter createXMLStreamWriter(final Result out) throws XMLStreamException
{
-        return FACTORY.createXMLStreamWriter(out);
+    public static XMLEventWriter createXMLEventWriter(final Result out) throws XMLStreamException
{
+        return FACTORY.createXMLEventWriter(out);
     }
 }

Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java?rev=1823504&r1=1823503&r2=1823504&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java
[UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java
[UTF-8] Wed Feb  7 18:01:51 2018
@@ -243,8 +243,8 @@ abstract class Pooled {
     /**
      * Returns the {@code FilterVersion} enumeration value to use for the current GML or
metadata version,
      * or {@code null} if the SIS native versions are suitable. If this method returns a
non-null value,
-     * then the output generated by JAXB will need to go through a {@link FilteredStreamWriter}
in order
-     * to replace the namespace of versions implemented by SIS by the namespace of versions
requested by
+     * then the output generated by JAXB will need to go through a {@link FilteredWriter}
in order to
+     * replace the namespace of versions implemented by SIS by the namespace of versions
requested by
      * the user.
      *
      * @see FilteredNamespaces

Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java?rev=1823504&r1=1823503&r2=1823504&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java
[UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java
[UTF-8] Wed Feb  7 18:01:51 2018
@@ -48,12 +48,12 @@ import org.apache.sis.internal.jaxb.Type
  *       when the marshaller is recycled.</li>
  *   <li>Constructs a SIS {@link Context} object on marshalling, in order to give
  *       additional information to the SIS object being marshalled.</li>
- *   <li>Wraps the output stream in a {@link FilteredStreamWriter} if the desired GML
version
+ *   <li>Wraps the output stream in a {@link FilteredWriter} if the desired GML version
  *       in not the SIS native GML version.</li>
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -136,14 +136,14 @@ final class PooledMarshaller extends Poo
      * than the one supported natively by SIS, i.e. when {@link #getFilterVersion()} returns
      * a non-null value.
      *
-     * @param object   the object to marshall.
+     * @param object   the object to marshal.
      * @param output   the writer created by SIS (<b>not</b> the writer given
by the user).
      * @param version  identifies the namespace substitutions to perform.
      */
-    private void marshal(final Object object, XMLStreamWriter output, final FilterVersion
version)
+    private void marshal(final Object object, XMLEventWriter output, final FilterVersion
version)
             throws XMLStreamException, JAXBException
     {
-        output = new FilteredStreamWriter(output, version);
+        output = new FilteredWriter(output, version);
         final Context context = begin();
         try {
             marshaller.marshal(toImplementation(object), output);
@@ -160,7 +160,7 @@ final class PooledMarshaller extends Poo
     public void marshal(final Object object, final Result output) throws JAXBException {
         final FilterVersion version = getFilterVersion();
         if (version != null) try {
-            marshal(object, OutputFactory.createXMLStreamWriter(output), version);
+            marshal(object, OutputFactory.createXMLEventWriter(output), version);
         } catch (XMLStreamException e) {
             throw new JAXBException(e);
         } else {
@@ -181,7 +181,7 @@ final class PooledMarshaller extends Poo
     public void marshal(final Object object, final OutputStream output) throws JAXBException
{
         final FilterVersion version = getFilterVersion();
         if (version != null) try {
-            marshal(object, OutputFactory.createXMLStreamWriter(output, getEncoding()), version);
+            marshal(object, OutputFactory.createXMLEventWriter(output, getEncoding()), version);
         } catch (XMLStreamException e) {
             throw new JAXBException(e);
         } else {
@@ -203,7 +203,7 @@ final class PooledMarshaller extends Poo
         final FilterVersion version = getFilterVersion();
         if (version != null) try {
             try (OutputStream s = new BufferedOutputStream(new FileOutputStream(output)))
{
-                marshal(object, OutputFactory.createXMLStreamWriter(s, getEncoding()), version);
+                marshal(object, OutputFactory.createXMLEventWriter(s, getEncoding()), version);
             }
         } catch (IOException | XMLStreamException e) {
             throw new JAXBException(e);
@@ -225,7 +225,7 @@ final class PooledMarshaller extends Poo
     public void marshal(final Object object, final Writer output) throws JAXBException {
         final FilterVersion version = getFilterVersion();
         if (version != null) try {
-            marshal(object, OutputFactory.createXMLStreamWriter(output), version);
+            marshal(object, OutputFactory.createXMLEventWriter(output), version);
         } catch (XMLStreamException e) {
             throw new JAXBException(e);
         } else {
@@ -246,7 +246,7 @@ final class PooledMarshaller extends Poo
     public void marshal(final Object object, final ContentHandler output) throws JAXBException
{
         final FilterVersion version = getFilterVersion();
         if (version != null) try {
-            marshal(object, OutputFactory.createXMLStreamWriter(output), version);
+            marshal(object, OutputFactory.createXMLEventWriter(output), version);
         } catch (XMLStreamException e) {
             throw new JAXBException(e);
         } else {
@@ -267,7 +267,7 @@ final class PooledMarshaller extends Poo
     public void marshal(final Object object, final Node output) throws JAXBException {
         final FilterVersion version = getFilterVersion();
         if (version != null) try {
-            marshal(object, OutputFactory.createXMLStreamWriter(output), version);
+            marshal(object, OutputFactory.createXMLEventWriter(output), version);
         } catch (XMLStreamException e) {
             throw new JAXBException(e);
         } else {
@@ -285,27 +285,10 @@ final class PooledMarshaller extends Poo
      * Delegates the marshalling to the wrapped marshaller.
      */
     @Override
-    public void marshal(final Object object, XMLStreamWriter output) throws JAXBException
{
-        final FilterVersion version = getFilterVersion();
-        if (version != null) {
-            output = new FilteredStreamWriter(output, version);
-        }
-        final Context context = begin();
-        try {
-            marshaller.marshal(toImplementation(object), output);
-        } finally {
-            context.finish();
-        }
-    }
-
-    /**
-     * Delegates the marshalling to the wrapped marshaller.
-     */
-    @Override
-    public void marshal(final Object object, final XMLEventWriter output) throws JAXBException
{
+    public void marshal(final Object object, final XMLStreamWriter output) throws JAXBException
{
         final FilterVersion version = getFilterVersion();
         if (version != null) try {
-            marshal(object, OutputFactory.createXMLStreamWriter(output), version);
+            marshal(object, OutputFactory.createXMLEventWriter(output), version);
         } catch (XMLStreamException e) {
             throw new JAXBException(e);
         } else {
@@ -319,6 +302,23 @@ final class PooledMarshaller extends Poo
         }
     }
 
+    /**
+     * Delegates the marshalling to the wrapped marshaller.
+     */
+    @Override
+    public void marshal(final Object object, XMLEventWriter output) throws JAXBException
{
+        final FilterVersion version = getFilterVersion();
+        if (version != null) {
+            output = new FilteredWriter(output, version);
+        }
+        final Context context = begin();
+        try {
+            marshaller.marshal(toImplementation(object), output);
+        } finally {
+            context.finish();
+        }
+    }
+
     /**
      * Delegates the marshalling to the wrapped marshaller.
      */



Mime
View raw message