Author: desruisseaux
Date: Wed Feb 14 21:42:41 2018
New Revision: 1824269

URL: http://svn.apache.org/viewvc?rev=1824269&view=rev
Log:
More stable mapping of namespace URIs to prefixes.

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/FilteredNamespaces.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/test/java/org/apache/sis/xml/FilteredNamespacesTest.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=1824269&r1=1824268&r2=1824269&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] Wed Feb 14 21:42:41 2018
@@ -20,7 +20,6 @@ import java.util.Map;
 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;
 
@@ -238,28 +237,6 @@ final class FilterVersion {
     }
 
     /**
-     * Returns the name (prefix, namespace and local part) to write in the XML 
document.
-     * This method may replace the namespace, and in some case the name local 
part too.
-     * If there is no name change, then this method returns the given instance 
as-is.
-     */
-    final QName export(QName name) {
-        String uri = name.getNamespaceURI();
-        if (uri != null && !uri.isEmpty()) {                                // 
Optimization for a common case.
-            final Replacement r = exports.get(uri);
-            if (r != null) {
-                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;
-    }
-
-    /**
      * Converts a namespace used in JAXB annotation to the namespace used in 
XML document.
      * Returns the same URI if there is no replacement.
      */

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=1824269&r1=1824268&r2=1824269&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] Wed Feb 14 21:42:41 2018
@@ -246,24 +246,37 @@ abstract class FilteredEvent<E extends X
         }
 
         /**
-         * Gets a read-only namespace context. Default implementation is 
suitable for exports
-         * (i.e. write operation). Needs to be overridden for imports (read 
operation).
+         * Gets the URI used by JAXB annotations for the given prefix used in 
the XML document.
+         * Returns {@code null} if no unique URI can be provided for the given 
prefix.
          *
-         * @see FilteredWriter#getNamespaceContext()
+         * <div class="note"><b>Example:</b>
+         * the {@code "gmd"} prefix from legacy ISO 19139:2007 standard can 
map to the
+         * {@code "http://standards.iso.org/iso/19115/-3/mdb/1.0"}, {@code 
"…/cit/1.0"}
+         * and other namespaces in ISO 19115-3:2016. Because of this ambiguity,
+         * this method returns {@code null}.</div>
+         *
+         * <p>At unmarshalling time, events are created by an arbitrary {@link 
javax.xml.stream.XMLEventReader}
+         * with namespaces used in the XML document. {@link FilteredReader} 
wraps those events using this class
+         * for converting the XML namespaces to the namespaces used by JAXB 
annotations.</p>
+         *
+         * <p>At marshalling time, events are created by JAXB using namespaces 
used in JAXB annotations.
+         * {@link FilteredWriter} wraps those events for converting those 
namespaces to the ones used in
+         * the XML document. This is the opposite than the work performed by 
this default implementation
+         * and must be handled by a {@code Start} subclass.</p>
          */
         @Override
-        public NamespaceContext getNamespaceContext() {
-            return FilteredNamespaces.exportNS(event.getNamespaceContext(), 
version);
+        public String getNamespaceURI(final String prefix) {
+            return version.importNS(event.getNamespaceURI(prefix));
         }
 
         /**
-         * 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).
+         * Returns a context mapping prefixes used in XML document to 
namespaces used in JAXB annotations.
+         * The {@code FilteredNamespaces.Inverse.getNamespaceURI(String)} 
method in that context shall do
+         * the same work than {@link #getNamespaceURI(String)} in this event.
          */
         @Override
-        public String getNamespaceURI(final String prefix) {
-            return version.exportNS(event.getNamespaceURI(prefix));
+        public NamespaceContext getNamespaceContext() {
+            return FilteredNamespaces.asJAXB(event.getNamespaceContext(), 
version);
         }
 
         /**
@@ -280,30 +293,4 @@ 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/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=1824269&r1=1824268&r2=1824269&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 14 21:42:41 2018
@@ -26,8 +26,7 @@ import javax.xml.XMLConstants;
 /**
  * In the associations between prefixes and namespaces, substitutes the 
namespaces used in JAXB annotations by the
  * namespaces used in the XML document at marshalling time. This class is used 
internally by {@link FilteredReader}
- * 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.
+ * and {@link FilteredWriter} only.
  *
  * <div class="section">The problem</div>
  * When the XML schemas of an international standard is updated, the URL of 
the namespace is often modified.
@@ -51,7 +50,7 @@ import javax.xml.XMLConstants;
  *
  * An alternative is to support only one version of each standard, and 
transform XML documents before unmarshalling
  * or after marshalling if they use different versions of standards. We could 
use XSLT for that, but this is heavy.
- * A lighter approach is to use {@link javax.xml.stream.XMLStreamReader} and 
{@link javax.xml.stream.XMLStreamWriter}
+ * A lighter approach is to use {@link javax.xml.stream.XMLEventReader} and 
{@link javax.xml.stream.XMLEventWriter}
  * as "micro-transformers".
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -66,23 +65,19 @@ class FilteredNamespaces implements Name
     /**
      * Given the context for namespaces used in our JAXB annotations, returns 
a context working with namespaces
      * used in XML document. The returned context converts namespace arguments 
from XML to JAXB namespaces, and
-     * converts returned namespaces from JAXB to XML. Prefixes are left 
unchanged since they can be arbitrary
-     * (even if not the standard match for a given namespace).
+     * converts returned namespaces from JAXB to XML.
      *
      * <div class="note"><b>Example:</b>
      * for a {@code "http://www.isotc211.org/2005/gmd"} namespace (legacy ISO 
19139:2007) given in argument to
-     * {@link #getPrefixes(String)}, the context convert that namespace to all 
possible ISO 19115-3 namespaces
+     * {@link #getPrefixes(String)}, the context converts that namespace to 
all possible ISO 19115-3 namespaces
      * (there is many) and returns the associated prefixes: {@code "mdb"}, 
{@code "cit"}, <i>etc.</i>
      * Conversely given a {@code "mdb"}, {@code "cit"}, <i>etc.</i>, prefix, 
{@link #getNamespaceURI(String)}
      * method returns the above-cited legacy GMD namespace.</div>
-     *
-     * This can be used when a {@link javax.xml.stream.XMLStreamWriter} has 
been created for writing with JAXB
-     * annotations and we want to expose an {@code XMLStreamReader} for 
writing a legacy XML document.
      */
-    static NamespaceContext exportNS(NamespaceContext context, final 
FilterVersion version) {
+    static NamespaceContext asXML(NamespaceContext context, final 
FilterVersion version) {
         if (context != null) {
-            if (context instanceof Import && ((Import) context).version == 
version) {
-                context = ((Import) context).context;
+            if (context instanceof Inverse && ((Inverse) context).version == 
version) {
+                context = ((Inverse) context).context;
             } else {
                 context = new FilteredNamespaces(context, version);
             }
@@ -93,16 +88,17 @@ class FilteredNamespaces implements Name
     /**
      * Given a context for namespaces used in XML document, returns a context 
working with the namespaces used
      * in our JAXB annotations.  The returned context converts namespace 
arguments from JAXB to XML namespaces
-     * before to delegate to the wrapped context, and converts returned 
namespaces from XML to JAXB.  This can
-     * be used when a {@link javax.xml.stream.XMLStreamReader} has been 
created for reading a legacy XML document
-     * and we want to expose an {@code XMLStreamReader} for JAXB.
+     * before to delegate to the wrapped context, and converts returned 
namespaces from XML to JAXB.
+     *
+     * <p>This can be used when a {@link javax.xml.stream.XMLEventWriter} has 
been created for writing a legacy
+     * XML document and we want to expose a {@link FilteredWriter} view to 
give to JAXB.</p>
      */
-    static NamespaceContext importNS(NamespaceContext context, final 
FilterVersion version) {
+    static NamespaceContext asJAXB(NamespaceContext context, final 
FilterVersion version) {
         if (context != null) {
             if (context.getClass() == FilteredNamespaces.class && 
((FilteredNamespaces) context).version == version) {
                 context = ((FilteredNamespaces) context).context;
             } else {
-                context = new FilteredNamespaces.Import(context, version);
+                context = new FilteredNamespaces.Inverse(context, version);
             }
         }
         return context;
@@ -111,8 +107,7 @@ class FilteredNamespaces implements Name
     /**
      * The context to wrap, given by {@link FilteredReader} or {@link 
FilteredWriter}.
      *
-     * @see javax.xml.stream.XMLStreamReader#getNamespaceContext()
-     * @see javax.xml.stream.XMLStreamWriter#getNamespaceContext()
+     * @see javax.xml.stream.XMLEventWriter#getNamespaceContext()
      */
     final NamespaceContext context;
 
@@ -131,13 +126,14 @@ class FilteredNamespaces implements Name
 
     /**
      * Substitutes the XML namespaces used in XML documents by namespaces used 
in JAXB annotations.
-     * This is used at unmarshalling time for importing legacy documents, 
performing the reverse of
-     * {@link FilteredNamespaces}. The <i>namespace → prefix</i> mapping is 
simpler because various
-     * ISO 19115-3 namespaces are mapped to the same legacy {@code "gmd"} 
prefix.
+     * This is used at marshalling time for exporting legacy documents, 
performing the reverse of
+     * {@link FilteredNamespaces}. The <i>namespace → prefix</i> mapping is 
simple because various
+     * ISO 19115-3 namespaces are mapped to the same legacy {@code "gmd"} 
prefix, but the reverse
+     * operation (<i>prefix → namespace</i> mapping) can often not be resolved.
      */
-    private static final class Import extends FilteredNamespaces {
+    private static final class Inverse extends FilteredNamespaces {
         /** Creates a new namespaces filter for the given source version. */
-        Import(final NamespaceContext context, final FilterVersion version) {
+        Inverse(final NamespaceContext context, final FilterVersion version) {
             super(context, version);
         }
 
@@ -148,6 +144,8 @@ class FilteredNamespaces implements Name
          *
          * <p>Except for {@code NULL_NS_URI}, this is usually an 
<cite>injective</cite> function:
          * each namespace can be created from at most one prefix.</p>
+         *
+         * @see FilteredEvent.Start#getNamespaceURI(String)
          */
         @Override public String getNamespaceURI(final String prefix) {
             return version.importNS(context.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=1824269&r1=1824268&r2=1824269&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] Wed Feb 14 21:42:41 2018
@@ -225,12 +225,21 @@ final class FilteredReader extends Filte
     private final List<QName> outerElements;
 
     /**
+     * The prefixes for namespace URIs. Keys are URIs used in JAXB annotations 
and values are prefixes
+     * computed by {@link Namespaces#getPreferredPrefix(String, String)} or 
any other means. We store
+     * the prefix both for performance reasons and for improving the 
guarantees that the URI → prefix
+     * mapping is stable.
+     */
+    private final Map<String,String> prefixes;
+
+    /**
      * Creates a new filter for the given version of the standards.
      */
     FilteredReader(final XMLEventReader in, final FilterVersion version) {
         super(version);
         this.in = in;
         outerElements = new ArrayList<>();
+        prefixes = new HashMap<>();
     }
 
     /**
@@ -289,7 +298,7 @@ final class FilteredReader extends Filte
                 break;
             }
             case NAMESPACE: {
-                event = importNS((Namespace) event);
+                event = importNS((Namespace) event, null, null);
                 break;
             }
             case START_ELEMENT: {
@@ -303,9 +312,10 @@ final class FilteredReader extends Filte
                     changed |= (a != ae);
                     renamedAttributes.add(ae);
                 }
-                final List<Namespace> namespaces = importNS(e.getNamespaces(), 
changed);
+                final List<Namespace> namespaces = importNS(e.getNamespaces(),
+                        originalName.getNamespaceURI(), 
name.getNamespaceURI(), changed);
                 if (namespaces != null) {
-                    event = new FilteredEvent.Import(e, name, namespaces, 
attributes(), version);
+                    event = new FilteredEvent.Start(e, name, namespaces, 
attributes(), version);
                 } else {
                     renamedAttributes.clear();
                 }
@@ -318,7 +328,8 @@ final class FilteredReader extends Filte
                 final EndElement e = event.asEndElement();
                 final QName originalName = e.getName();
                 final QName name = convert(originalName);
-                final List<Namespace> namespaces = importNS(e.getNamespaces(), 
name != originalName);
+                final List<Namespace> namespaces = importNS(e.getNamespaces(),
+                        originalName.getNamespaceURI(), 
name.getNamespaceURI(), name != originalName);
                 if (namespaces != null) {
                     event = new FilteredEvent.End(e, name, namespaces);
                 }
@@ -350,7 +361,7 @@ final class FilteredReader extends Filte
      * @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 QName convert(QName name) {
+    private QName convert(final 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);
@@ -395,16 +406,17 @@ final class FilteredReader extends Filte
         if (namespace == null) {
             namespace = version.importNS(oldNS);
         }
+        if (namespace.equals(oldNS) && localPart.equals(name.getLocalPart())) {
+            return name;
+        }
         /*
-         * 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.
+         * Build a new name if any component (URI or local part) changed. The 
prefix should have
+         * been specified (indirectly) by a previous call to 
'importNS(Namespace)', for example
+         * as a result of a NAMESPACE event. If not, we compute it now using 
the same method.
          */
-        if (!namespace.equals(oldNS) || 
!localPart.equals(name.getLocalPart())) {
-            name = new QName(namespace, localPart);
-        }
-        return name;
+        final String prefix = prefixes.computeIfAbsent(namespace,
+                (ns) -> Namespaces.getPreferredPrefix(ns, name.getPrefix()));
+        return new QName(namespace, localPart, prefix);
     }
 
     /**
@@ -421,18 +433,28 @@ final class FilteredReader extends Filte
     }
 
     /**
-     * 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.
+     * Converts a namespace read from the XML document to the namespace used 
by JAXB annotations.
+     * This methods can convert the namespace for which there is a bijective 
mapping, for example
+     * {@code "http://www.isotc211.org/2005/gco"} to {@code 
"http://standards.iso.org/iso/19115/-3/gco/1.0"}.
+     * However some namespaces like {@code "http://www.isotc211.org/2005/gmd"} 
may be left unchanged,
+     * because that namespace from legacy ISO 19139:2007 can be mapped to many 
different namespaces
+     * in newer ISO 19115-3:2016 standard. However in some cases the context 
allows us to determines
+     * which newer namespace is used. In such case, that mapping is specified 
by the
+     * ({@code oldURI}, {@code newURI}) pair.
      *
      * @param  namespace  the namespace to import.
+     * @param  oldURI     an old URI which has been renamed as {@code newURI}, 
or {@code null} if none.
+     * @param  newURI     the new URI for {@code oldURI}, or {@code null} if 
{@code newURI} is null.
      */
-    private Namespace importNS(final Namespace namespace) {
+    private Namespace importNS(final Namespace namespace, final String oldURI, 
final String newURI) {
         String uri = namespace.getNamespaceURI();
         if (uri != null && !uri.isEmpty()) {
             uri = FilteredXML.removeTrailingSlash(uri);
-            final String imported = version.importNS(uri);
+            final String imported = uri.equals(oldURI) ? newURI : 
version.importNS(uri);
             if (imported != uri) {
-                return new FilteredEvent.NS(namespace, 
Namespaces.getPreferredPrefix(imported, namespace.getPrefix()), imported);
+                final String prefix = prefixes.computeIfAbsent(imported,
+                        (ns) -> Namespaces.getPreferredPrefix(ns, 
namespace.getPrefix()));
+                return new FilteredEvent.NS(namespace, prefix, imported);
             }
         }
         return namespace;
@@ -442,17 +464,21 @@ final class FilteredReader extends Filte
      * Imports the namespaces read from the XML document.
      *
      * @param  namespaces  the namespaces to filter.
+     * @param  oldURI      an old URI which has been renamed as {@code 
newURI}, or {@code null} if none.
+     * @param  newURI      the new URI for {@code oldURI}, or {@code null} if 
{@code newURI} is null.
      * @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) {
+    private List<Namespace> importNS(final Iterator<Namespace> namespaces,
+            final String oldURI, final String newURI, 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)));
+            changed |= (namespace != (namespace = importNS(namespace, oldURI, 
newURI)));
             modified.add(namespace);
         } while (namespaces.hasNext());
         return changed ? modified : null;

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=1824269&r1=1824268&r2=1824269&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] Wed Feb 14 21:42:41 2018
@@ -65,7 +65,7 @@ final class FilteredWriter extends Filte
      *   <li>In the namespaces of a start element.</li>
      * </ul>
      */
-    private final Map<String, Namespace> uniqueNamespaces;
+    private final Map<String,Namespace> uniqueNamespaces;
 
     /**
      * Creates a new filter for the given version of the standards.
@@ -77,12 +77,48 @@ final class FilteredWriter extends Filte
     }
 
     /**
+     * Returns the name (prefix, namespace and local part) to write in the XML 
document.
+     * This method may replace the namespace, and in some case the name local 
part too.
+     * If there is no name change, then this method returns the given instance 
as-is.
+     */
+    private QName export(QName name) throws XMLStreamException {
+        String uri = name.getNamespaceURI();
+        if (uri != null && !uri.isEmpty()) {                                // 
Optimization for a common case.
+            final FilterVersion.Replacement r = version.export(uri);
+            if (r != null) {
+                uri = r.namespace;
+                /*
+                 * The wrapped XMLEventWriter maintains a mapping from 
prefixes to namespace URIs.
+                 * Arguments are exported URIs (e.g. from legacy ISO 
19139:2007) and return values
+                 * are prefixes computed by 'Namespaces.getPreferredPrefix(…)' 
or any other means.
+                 * We fetch those prefixes for performance reasons and for 
improving the guarantees
+                 * that the URI → prefix mapping is stable, since JAXB seems 
to require them for
+                 * writing namespaces in XML.
+                 */
+                String prefix = out.getPrefix(uri);
+                if (prefix == null) {
+                    prefix = Namespaces.getPreferredPrefix(uri, 
name.getPrefix());
+                    out.setPrefix(prefix, uri);
+                    /*
+                     * The above call for 'getPreferredPrefix' above is 
required: JAXB seems to need the prefixes
+                     * for recognizing namespaces. The prefix shall be 
computed in the same way than 'exportIfNew'.
+                     * We enter in this block only for the root element, 
before to parse 'xmlns' attributes. For
+                     * all other elements after the root elements, above call 
to 'out.getPrefix(uri)' should succeed.
+                     */
+                }
+                name = new QName(uri, r.exportProperty(name.getLocalPart()), 
prefix);
+            }
+        }
+        return name;
+    }
+
+    /**
      * 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) {
+    private Attribute export(Attribute attribute) throws XMLStreamException {
         final QName originalName = attribute.getName();
-        final QName name = version.export(originalName);
+        final QName name = export(originalName);
         if (name != originalName) {
             attribute = new FilteredEvent.Attr(attribute, name);
         }
@@ -104,6 +140,10 @@ final class FilteredWriter extends Filte
             if (exported != uri) {
                 return uniqueNamespaces.computeIfAbsent(exported, (k) -> {
                     return new FilteredEvent.NS(namespace, 
Namespaces.getPreferredPrefix(k, namespace.getPrefix()), k);
+                    /*
+                     * The new prefix selected by above line will be saved by 
out.add(Namespace)
+                     * after this method has been invoked by the NAMESPACE 
case of add(XMLEvent).
+                     */
                 });
             }
             final Namespace c = uniqueNamespaces.put(uri, namespace);   // No 
namespace change needed. Overwrite wrapper if any.
@@ -165,7 +205,7 @@ final class FilteredWriter extends Filte
                 uniqueNamespaces.clear();                       // Discard 
entries created by NAMESPACE events.
                 final StartElement e = event.asStartElement();
                 final QName originalName = e.getName();
-                final QName name = version.export(originalName);
+                final QName name = export(originalName);
                 boolean changed = name != originalName;
                 for (final Iterator<Attribute> it = e.getAttributes(); 
it.hasNext();) {
                     final Attribute a = it.next();
@@ -175,7 +215,7 @@ final class FilteredWriter extends Filte
                 }
                 final List<Namespace> namespaces = export(e.getNamespaces(), 
changed);
                 if (namespaces != null) {
-                    event = new FilteredEvent.Start(e, name, namespaces, 
attributes(), version);
+                    event = new Event(e, name, namespaces, attributes(), 
version);
                 } else {
                     renamedAttributes.clear();
                 }
@@ -184,7 +224,7 @@ final class FilteredWriter extends Filte
             case END_ELEMENT: {
                 final EndElement e = event.asEndElement();
                 final QName originalName = e.getName();
-                final QName name = version.export(originalName);
+                final QName name = export(originalName);
                 final List<Namespace> namespaces = export(e.getNamespaces(), 
name != originalName);
                 if (namespaces != null) {
                     event = new FilteredEvent.End(e, name, namespaces);
@@ -250,18 +290,60 @@ final class FilteredWriter extends Filte
      * <p>Implemented as a matter of principle, but JAXB did not invoked this 
method in our tests.</p>
      */
     @Override
-    public void setNamespaceContext(NamespaceContext context) throws 
XMLStreamException {
-        out.setNamespaceContext(FilteredNamespaces.importNS(context, version));
+    public void setNamespaceContext(final NamespaceContext context) throws 
XMLStreamException {
+        out.setNamespaceContext(FilteredNamespaces.asXML(context, version));
     }
 
     /**
-     * Returns the context of the underlying writer wrapped in a filter that 
convert the namespaces on the fly.
+     * Returns a naming context suitable for consumption by JAXB marshallers.
+     * The {@link XMLEventWriter} wrapped by this {@code FilteredWriter} has 
been created for writing in a file.
+     * Consequently its naming context manages namespaces used in the XML 
document. But the JAXB marshaller using
+     * this {@code FilteredWriter} facade expects the namespaces declared in 
JAXB annotations. Consequently this
+     * method returns an adapter that converts namespaces on the fly.
      *
-     * @see FilteredEvent.Start#getNamespaceContext()
+     * @see Event#getNamespaceContext()
      */
     @Override
     public NamespaceContext getNamespaceContext() {
-        return FilteredNamespaces.exportNS(out.getNamespaceContext(), version);
+        return FilteredNamespaces.asJAXB(out.getNamespaceContext(), version);
+    }
+
+    /**
+     * Wraps the {@link StartElement} produced by JAXB for using the 
namespaces used in the XML document.
+     */
+    private static final class Event extends FilteredEvent.Start {
+        /** Wraps the given event with potentially different name, namespaces 
and attributes. */
+        Event(StartElement event, QName name, List<Namespace> namespaces, 
List<Attribute> attributes, FilterVersion version) {
+            super(event, name, namespaces, attributes, version);
+        }
+
+        /**
+         * Gets the URI used in the XML document for the given prefix used in 
JAXB annotations.
+         * At marshalling time, events are created by JAXB using namespaces 
used in JAXB annotations.
+         * {@link FilteredWriter} wraps those events for converting those 
namespaces to the ones used
+         * in the XML document.
+         *
+         * <div class="note"><b>Example:</b> the {@code "cit"} prefix from ISO 
19115-3:2016 standard
+         * represents the {@code 
"http://standards.iso.org/iso/19115/-3/mdb/1.0"} namespace, which is
+         * mapped to {@code "http://www.isotc211.org/2005/gmd"} in the legacy 
ISO 19139:2007 standard.
+         * That later URI is returned.</div>
+         */
+        @Override
+        public String getNamespaceURI(final String prefix) {
+            return version.exportNS(event.getNamespaceURI(prefix));
+        }
+
+        /**
+         * Returns a context mapping prefixes used in JAXB annotations to 
namespaces used in XML document.
+         * The {@link FilteredNamespaces#getNamespaceURI(String)} method in 
that context shall do the same
+         * work than {@link #getNamespaceURI(String)} in this event.
+         *
+         * @see FilteredNamespaces#getNamespaceURI(String)
+         */
+        @Override
+        public NamespaceContext getNamespaceContext() {
+            return FilteredNamespaces.asXML(event.getNamespaceContext(), 
version);
+        }
     }
 
     /**

Modified: 
sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java?rev=1824269&r1=1824268&r2=1824269&view=diff
==============================================================================
--- 
sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java
 [UTF-8] (original)
+++ 
sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/xml/FilteredNamespacesTest.java
 [UTF-8] Wed Feb 14 21:42:41 2018
@@ -50,8 +50,9 @@ public final strictfp class FilteredName
      * Tests {@link FilteredNamespaces#getPrefixes(String)}.
      */
     @Test
+    @SuppressWarnings("unchecked")      // TODO: remove with JDK9
     public void testGetPrefixes() {
-        final NamespaceContext fns = FilteredNamespaces.exportNS(this, 
FilterVersion.ISO19139);
+        final NamespaceContext fns = FilteredNamespaces.asXML(this, 
FilterVersion.ISO19139);
         final Iterator<String> it = fns.getPrefixes(LegacyNamespaces.GMD);
         final Set<String> prefixes = new HashSet<>();
         while (it.hasNext()) {
@@ -91,7 +92,7 @@ public final strictfp class FilteredName
      */
     @Test
     public void testGetPrefix() {
-        final NamespaceContext fns = FilteredNamespaces.exportNS(this, 
FilterVersion.ISO19139);
+        final NamespaceContext fns = FilteredNamespaces.asXML(this, 
FilterVersion.ISO19139);
         /*
          * Following tests are not really interesting since FilteredNamespaces,
          * after failing to find a mapping, just delegates to 
this.getPrefix(…).


Reply via email to