Author: desruisseaux
Date: Fri Feb 16 11:58:08 2018
New Revision: 1824452

URL: http://svn.apache.org/viewvc?rev=1824452&view=rev
Log:
Allow re-ordering of elements at marshalling time. This allow 
DefaultServiceIdentificationTest to pass.

Modified:
    
sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/FilteredWriter.java

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=1824452&r1=1824451&r2=1824452&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] Fri Feb 16 11:58:08 2018
@@ -17,8 +17,12 @@
 package org.apache.sis.xml;
 
 import java.util.Map;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.Set;
 import java.util.List;
+import java.util.Queue;
+import java.util.ArrayDeque;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
@@ -52,6 +56,35 @@ import static javax.xml.stream.XMLStream
  */
 final class FilteredWriter extends FilteredXML implements XMLEventWriter {
     /**
+     * Elements that appear in different order in ISO 19139:2007 (or other 
legacy standards) compared
+     * to ISO 19115-3:2016 (or other newer standards). Key are names of 
elements to reorder. Values
+     * are the elements to skip before to write the element to reorder.
+     *
+     * <div class="note"><b>Example:</b>
+     * In {@code SV_ServiceIdentification}, {@code <srv:couplingType>} appears 
before {@code <srv:coupledResource>}
+     * according ISO 19115-3:2016. But in ISO 19139:2007, it was the reverse 
order. Since Apache SIS writes elements
+     * in the order defined by the newer standard, {@code <couplingType>} is 
encountered first. The set associated
+     * to that key tells us that, when writing legacy ISO 19139 document, we 
should skip {@code <coupledResource>}
+     * before to write {@code <srv:couplingType>}.</div>
+     *
+     * While this map is used for reordering elements according legacy 
standards, the {@link QName} keys and values
+     * use the namespaces of newer standards. This is because newer standards 
like ISO 19115-3 uses many namespaces
+     * where legacy ISO 19139:2007 used only one namespace, so using the newer 
names reduce the risk of confusion.
+     *
+     * @see #toSkip
+     * @see #deferred
+     *
+     * @todo Hard-coded for now. Should move to a resource file in a future 
version.
+     */
+    private static final Map<QName, Set<QName>> ELEMENTS_TO_REORDER;
+    static {
+        final Map<QName, Set<QName>> m = new HashMap<>(4);
+        m.put(new QName(Namespaces.SRV, "couplingType", "srv"), 
Collections.singleton(new QName(Namespaces.SRV, "coupledResource", "srv")));
+        m.put(new QName(Namespaces.SRV, "connectPoint", "srv"), 
Collections.singleton(new QName(Namespaces.SRV, "parameter",       "srv")));
+        ELEMENTS_TO_REORDER = m;
+    }
+
+    /**
      * Where events are sent.
      */
     private final XMLEventWriter out;
@@ -68,12 +101,52 @@ final class FilteredWriter extends Filte
     private final Map<String,Namespace> uniqueNamespaces;
 
     /**
+     * {@code true} if events should be sent to {@link #deferred} instead than 
to {@link #out}.
+     * This is set to {@code true} when we see a {@link StartElement} having 
one of the names
+     * contained in the {@link #ELEMENTS_TO_REORDER} keys set.
+     */
+    private boolean isDeferring;
+
+    /**
+     * Events for which writing is deferred as long as there is elements 
{@linkplain #toSkip to skip}.
+     * The intent is to reorder elements that appear in a different order in 
legacy standards compared
+     * to newer standards. This is a FIFO (First-In-First-Out) queue. 
Namespaces are the exported ones
+     * (the ones after conversions from JAXB to the XML document to write).
+     *
+     * @see #ELEMENTS_TO_REORDER
+     */
+    private final Queue<XMLEvent> deferred;
+
+    /**
+     * If non-null, elements to skip before we can write the {@linkplain 
#deferred} events.
+     * Should be the {@link #ELEMENTS_TO_REORDER} value associated to the 
element to defer.
+     * A null value means that events can be written immediately to {@link 
#out}.
+     */
+    private Set<QName> toSkip;
+
+    /**
+     * Name of the the root element of a sub-tree to handle in a special way, 
or {@code null} if none.
+     * At first, this is the name of the {@link StartElement} of a sub-tree to 
defer (i.e. one of the
+     * keys in the {@link #ELEMENTS_TO_REORDER} map). Later, it becomes the 
names of sub-trees to skip
+     * (i.e. the {@link #toSkip} values).
+     */
+    private QName subtreeRootName;
+
+    /**
+     * Number of times that {@link #subtreeRootName} has been found. A value 
of 1 means that we started
+     * receiving events for that subtree. A value of 0 means that we finished 
receiving events for that
+     * subtree. A value greater than 1 means that there is nested sub-trees 
(should not happen).
+     */
+    private int subtreeNesting;
+
+    /**
      * Creates a new filter for the given version of the standards.
      */
     FilteredWriter(final XMLEventWriter out, final FilterVersion version) {
         super(version);
         this.out = out;
         uniqueNamespaces = new LinkedHashMap<>();
+        deferred = new ArrayDeque<>();
     }
 
     /**
@@ -182,8 +255,11 @@ final class FilteredWriter extends Filte
     }
 
     /**
-     * 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.
+     * Converts an event from the namespaces used in JAXB annotations to the 
namespaces used in the XML document
+     * to write. This method may wrap the given event into another event for 
changing the namespace and prefix,
+     * or use the event as-is if no change is needed.
+     *
+     * @param  event  the event using JAXB namespaces.
      */
     @Override
     @SuppressWarnings("unchecked")      // TODO: remove on JDK9
@@ -201,6 +277,23 @@ final class FilteredWriter extends Filte
                 }
                 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);
+                }
+                if (toSkip != null) {                           // Check if a 
previous START_ELEMENT found a need to reorder.
+                    if (originalName.equals(subtreeRootName)) {
+                        subtreeNesting--;                       // May reach 0 
but be followed by another element to skip.
+                    } else if (subtreeNesting == 0) {
+                        writeDeferred();                        // About to 
exit the parent element containing deferred element.
+                    }
+                }
+                break;
+            }
             case START_ELEMENT: {
                 uniqueNamespaces.clear();                       // Discard 
entries created by NAMESPACE events.
                 final StartElement e = event.asStartElement();
@@ -219,20 +312,52 @@ final class FilteredWriter extends Filte
                 } else {
                     renamedAttributes.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);
+                /*
+                 * At this point, we finished to export the event (i.e. to 
convert namespaces to the URI
+                 * used in the XML documents). The remaining code in this 
block is for handling the case
+                 * where the elements should not be written immediately, but 
after some other events.
+                 * This happen when legacy standards ordered some elements 
differently.
+                 */
+                if (toSkip == null) {
+                    toSkip = ELEMENTS_TO_REORDER.get(originalName);
+                    if (toSkip != null) {
+                        subtreeRootName = originalName;                 // 
Found a new element to defer.
+                        subtreeNesting = 1;
+                        isDeferring = true;
+                    }
+                } else if (subtreeNesting == 0) {
+                    if (toSkip.contains(originalName)) {
+                        subtreeRootName = originalName;                 // 
Defer until after that element.
+                        subtreeNesting = 1;
+                    } else {
+                        writeDeferred();                                // End 
of deferring.
+                    }
+                } else if (originalName.equals(subtreeRootName)) {
+                    subtreeNesting++;
                 }
                 break;
             }
         }
-        out.add(event);
+        if (isDeferring) {
+            deferred.add(event);
+            isDeferring = (subtreeNesting != 0);
+        } else {
+            out.add(event);
+        }
+    }
+
+    /**
+     * Writes immediately all elements that were deferred. This happen because 
the next {@link StartElement}
+     * to write should be after the deferred element, or because we are about 
to exit the parent element that
+     * contains the deferred element, or because {@link #flush()} has been 
invoked.
+     *
+     * @see #ELEMENTS_TO_REORDER
+     */
+    private void writeDeferred() throws XMLStreamException {
+        subtreeRootName = null;
+        toSkip = null;
+        XMLEvent v;
+        while ((v = deferred.poll()) != null) out.add(v);
     }
 
     /**
@@ -351,6 +476,7 @@ final class FilteredWriter extends Filte
      */
     @Override
     public void flush() throws XMLStreamException {
+        writeDeferred();
         out.flush();
     }
 


Reply via email to