You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2018/02/17 16:08:28 UTC

svn commit: r1824604 - in /sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml: Transformer.java TransformingReader.java TransformingWriter.java

Author: desruisseaux
Date: Sat Feb 17 16:08:27 2018
New Revision: 1824604

URL: http://svn.apache.org/viewvc?rev=1824604&view=rev
Log:
Modify the algorithm for element renaming. The new algorithm avoid the need to maintain a map of class where a property may appear.

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

Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Transformer.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Transformer.java?rev=1824604&r1=1824603&r2=1824604&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Transformer.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/Transformer.java [UTF-8] Sat Feb 17 16:08:27 2018
@@ -26,6 +26,8 @@ import java.util.InvalidPropertiesFormat
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.LineNumberReader;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.events.Attribute;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
@@ -99,6 +101,34 @@ abstract class Transformer {
     final TransformVersion version;
 
     /**
+     * The outer element name together with its attribute. This is an element in a linked list.
+     */
+    private static final class OuterElement {
+        /** The outer element name as used in JAXB annotations. */
+        final QName name;
+
+        /** The attribute namespaces, as one of the values of the map loaded by {@link #load(String)}. */
+        final Map<String, String> attributeNS;
+
+        /** The previous outer element (in other words, the parent), or {@code null} if none. */
+        final OuterElement parent;
+
+        /** Creates a new outer element. */
+        private OuterElement(final OuterElement parent, final QName name, final Map<String, String> attributeNS) {
+            this.parent      = parent;
+            this.name        = name;
+            this.attributeNS = attributeNS;
+        }
+    }
+
+    /**
+     * Linked list of encountered XML tags, in reverse order. Used for backtracking.  Elements are removed from
+     * this list when they are closed. Names should be the ones we get after conversion from namespaces used in
+     * XML document to namespaces used in JAXB annotations.
+     */
+    private OuterElement outerElements;
+
+    /**
      * Temporary list of attributes after their namespace change.
      * This list is recycled for each XML element to be read or written.
      */
@@ -108,7 +138,7 @@ abstract class Transformer {
      * Creates a new XML reader or writer.
      */
     Transformer(final TransformVersion version) {
-        this.version = version;
+        this.version      = version;
         renamedAttributes = new ArrayList<>();
     }
 
@@ -230,4 +260,79 @@ abstract class Transformer {
         }
         return attributes;
     }
+
+    /**
+     * Returns {@code true} if an element with the given name is likely to be an OGC/ISO type (as opposed to property).
+     * For example given the following XML, this method returns {@code true} for {@code cit:CI_Date} but {@code false}
+     * for {@code cit:date}:
+     *
+     * {@preformat xml
+     *   <cit:CI_Citation>
+     *     <cit:date>
+     *       <cit:CI_Date>
+     *         …
+     *       </cit:CI_Date>
+     *     </cit:date>
+     *   </cit:CI_Citation>
+     * }
+     *
+     * This method is based on simple heuristic applicable to OGC/ISO conventions, and may change in any future SIS
+     * version depending on new formats to support. The intent is only to reduce the amount of nodes created in the
+     * {@link #outerElements} linked list.
+     */
+    static boolean isOuterElement(final String localPart) {
+        if (localPart.length() < 4) return false;
+        final char c = localPart.charAt(0);
+        return (c >= 'A' && c <= 'Z');
+    }
+
+    /**
+     * Notifies that we are opening an element of the given name.
+     *
+     * @param  name        element name as declared in JAXB annotations.
+     * @param  namespaces  namespaces map loaded by {@link #load(String)}.
+     */
+    final void open(final QName name, final Map<String, Map<String,String>> namespaces) {
+        final String localPart = name.getLocalPart();
+        if (isOuterElement(localPart)) {
+            outerElements = new OuterElement(outerElements, name,
+                    namespaces.getOrDefault(localPart, Collections.emptyMap()));
+        }
+    }
+
+    /**
+     * Notifies that we are closing an element of the given name. This method closes the last start element
+     * with a matching name. It should be the last element on the list in a well-formed XML, but we loop in
+     * the list anyway as a safety.
+     *
+     * @param  name  element name as declared in JAXB annotations.
+     */
+    final void close(final QName name) {
+        if (isOuterElement(name.getLocalPart())) {
+            OuterElement e = outerElements;
+            while (e != null) {
+                if (name.equals(e.name)) {
+                    outerElements = e.parent;       // Discard e and all children of e.
+                    break;
+                }
+                e = e.parent;
+            }
+        }
+    }
+
+    /**
+     * Attributes expected in the namespace of current element. This method may return a different {@code Map}
+     * instance after each call to {@link #open(QName, Map)} or {@link #close(QName)}. The returned map shall
+     * not be modified.
+     */
+    final Map<String,String> attributeNS() {
+        return (outerElements != null) ? outerElements.attributeNS : Collections.emptyMap();
+    }
+
+    /**
+     * Frees any resources associated with this reader.
+     */
+    public void close() throws XMLStreamException {
+        outerElements = null;
+    }
 }

Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/TransformingReader.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/TransformingReader.java?rev=1824604&r1=1824603&r2=1824604&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/TransformingReader.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/TransformingReader.java [UTF-8] Sat Feb 17 16:08:27 2018
@@ -16,10 +16,8 @@
  */
 package org.apache.sis.xml;
 
-import java.util.Set;
 import java.util.Map;
 import java.util.List;
-import java.util.HashSet;
 import java.util.HashMap;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -32,7 +30,6 @@ import javax.xml.stream.events.Attribute
 import javax.xml.stream.events.Namespace;
 import javax.xml.stream.events.EndElement;
 import javax.xml.stream.events.StartElement;
-import org.apache.sis.internal.util.CollectionsExt;
 
 import static javax.xml.stream.XMLStreamConstants.*;
 
@@ -99,48 +96,11 @@ final class TransformingReader extends T
     }
 
     /**
-     * The mapping from attribute names to types where such attribute is declared.
-     * An attribute of the same name may be declared in many types.
-     *
-     * <p>This method does not look at namespaces. Consequently the renaming encoding
-     * (e.g. {@code "DCP/distributedComputingPlatform"}) is not of concern here.</p>
-     */
-    private static final Map<String, Set<String>> DECLARING_TYPES;
-    static {
-        final Map<String, Set<String>> m = new HashMap<>(500);
-        for (final Map.Entry<String, Map<String,String>> forTypes : NAMESPACES.entrySet()) {
-            final String type = forTypes.getKey();
-            for (final String attribute : forTypes.getValue().keySet()) {
-                m.computeIfAbsent(attribute, (k) -> new HashSet<>()).add(type);
-            }
-        }
-        /*
-         * At this point we finished computing the map values. Many values are the same sets.
-         * For example the {CI_Citation} singleton set occurs often. Following code replaces
-         * duplicated sets by shared instances in order to save a little bit of space.
-         */
-        final Map<Set<String>, Set<String>> unique = new HashMap<>(200);
-        m.replaceAll((k, v) -> {
-            v = CollectionsExt.compact(v);
-            final Set<String> r = unique.putIfAbsent(v, v);
-            return (r != null) ? r : v;
-        });
-        DECLARING_TYPES = m;
-    }
-
-    /**
      * The reader from which to read events.
      */
     private final XMLEventReader in;
 
     /**
-     * List of encountered XML tags, in order. Used for backtracking. Elements are removed from this list
-     * when they are closed. Names should be the ones we get after conversion from namespaces used in XML
-     * document to namespaces used in JAXB annotations.
-     */
-    private final List<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
@@ -152,7 +112,7 @@ final class TransformingReader extends T
      * The next event to return after a call to {@link #peek()}. This is used for avoiding to recompute
      * the same object many times when {@link #peek()} is invoked before a call to {@link #nextEvent()}.
      * This is also required for avoiding to duplicate additions and removals of elements in the
-     * {@link #outerElements} list.
+     * {@code outerElements} list.
      */
     private XMLEvent nextEvent;
 
@@ -162,7 +122,6 @@ final class TransformingReader extends T
     TransformingReader(final XMLEventReader in, final TransformVersion version) {
         super(version);
         this.in = in;
-        outerElements = new ArrayList<>();
         prefixes = new HashMap<>();
     }
 
@@ -269,6 +228,7 @@ final class TransformingReader extends T
             case START_ELEMENT: {
                 final StartElement e = event.asStartElement();
                 final QName originalName = e.getName();
+                open(originalName, NAMESPACES);                 // Must be invoked before 'convert(QName)'.
                 final QName name = convert(originalName);
                 boolean changed = name != originalName;
                 for (final Iterator<Attribute> it = e.getAttributes(); it.hasNext();) {
@@ -284,7 +244,6 @@ final class TransformingReader extends T
                 } else {
                     renamedAttributes.clear();
                 }
-                outerElements.add(name);
                 break;
             }
             case END_ELEMENT: {
@@ -296,16 +255,7 @@ final class TransformingReader extends T
                 if (namespaces != null) {
                     event = new TransformedEvent.End(e, name, namespaces);
                 }
-                /*
-                 * Close the last start element with a matching name. It should be the last element
-                 * on the list in a well-formed XML, but we loop in the list anyway as a safety.
-                 */
-                for (int i = outerElements.size(); --i >= 0;) {
-                    if (name.equals(outerElements.get(i))) {
-                        outerElements.remove(i);
-                        break;
-                    }
-                }
+                close(originalName);                        // Must be invoked only after 'convert(QName)'
                 break;
             }
         }
@@ -323,39 +273,26 @@ final class TransformingReader extends T
      *          or {@code null} if the given name is unknown.
      */
     private QName convert(final QName name) {
-        String namespace = null;                        // In this method, null means no change to given name.
+        String namespace;                               // In this method, null means no change to given name.
         String localPart = name.getLocalPart();
-        final Set<String> declaringTypes = DECLARING_TYPES.get(localPart);
-        if (declaringTypes == null) {
-            /*
-             * If the element is a root element, return the associated namespace.
-             * We do not need to check if the value associated to TYPE_KEY is for
-             * a renaming since it is never the case for that key.
-             */
-            final Map<String,String> attributes = NAMESPACES.get(localPart);
-            if (attributes != null) {
-                namespace = attributes.get(TYPE_KEY);           // May be null.
-            }
+        /*
+         * If the element is a root element, return the associated namespace.
+         * We do not need to check if the value associated to TYPE_KEY is for
+         * a renaming since it is never the case for that key.
+         */
+        Map<String,String> attributeNS;
+        if (isOuterElement(localPart) && (attributeNS = NAMESPACES.get(localPart)) != null) {
+            namespace = attributeNS.get(TYPE_KEY);      // May be null.
         } else {
             /*
-             * If the element is not a root element, we need to backtrack until we find the latest
-             * possible parent element. Then, we use the namespace associated with that parent.
+             * If the element is not a root element, we need to use the map of attributes
+             * of the parent element.
              */
-            for (int i = outerElements.size(); --i >= 0;) {
-                final String parent = outerElements.get(i).getLocalPart();
-                if (declaringTypes.contains(parent)) {
-                    /*
-                     * A NullPointerException below would be a bug in our algorithm because
-                     * we constructed DECLARING_TYPES from NAMESPACES keys only.
-                     */
-                    final Map<String,String> attributes = NAMESPACES.get(parent);
-                    namespace = attributes.get(localPart);
-                    if (!isNamespace(namespace)) {
-                        localPart = namespace;
-                        namespace = attributes.get(namespace);
-                    }
-                    break;
-                }
+            attributeNS = attributeNS();
+            namespace = attributeNS.get(localPart);
+            if (namespace != null && !isNamespace(namespace)) {
+                localPart = namespace;
+                namespace = attributeNS.get(namespace);
             }
         }
         /*
@@ -448,8 +385,8 @@ final class TransformingReader extends T
     /**
      * Reads the content of a text-only element. Forwards from the underlying reader as-is.
      *
-     * @todo Untested. In particular, it is not clear how to update {@link #outerElements}.
-     *       By change, JAXB does not seem to invoke this method.
+     * @todo Untested. In particular, it is not clear how to update {@code outerElements}.
+     *       By chance, JAXB does not seem to invoke this method.
      */
     @Override
     public String getElementText() throws XMLStreamException {
@@ -470,7 +407,7 @@ final class TransformingReader extends T
      */
     @Override
     public void close() throws XMLStreamException {
-        outerElements.clear();
+        super.close();
         in.close();
     }
 }

Modified: sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/TransformingWriter.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/TransformingWriter.java?rev=1824604&r1=1824603&r2=1824604&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/TransformingWriter.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-utility/src/main/java/org/apache/sis/xml/TransformingWriter.java [UTF-8] Sat Feb 17 16:08:27 2018
@@ -56,6 +56,34 @@ import static javax.xml.stream.XMLStream
  */
 final class TransformingWriter extends Transformer implements XMLEventWriter {
     /**
+     * Location of the file listing types and their properties contained in legacy namespaces.
+     * This is used for mapping new ISO 19115-3:2016 namespaces to legacy ISO 19139:2007 ones,
+     * where the same {@code "http://standards.iso.org/iso/19115/-3/…"} URI is used in places
+     * where legacy schemas had two distinct URIs: {@code "http://www.isotc211.org/2005/gmd"}
+     * and {@code "http://standards.iso.org/iso/19115/-2/gmi/1.0"}.
+     */
+    static final String FILENAME = "ImageryExtensions.lst";
+
+    /**
+     * The mapping from (<var>type</var>, <var>attribute</var>) pairs to legacy namespaces.
+     *
+     * <ul>
+     *   <li>Keys are XML names of types, ignoring {@code "_TYPE"} suffix (e.g. {@code "MI_Georectified"})</li>
+     *   <li>Values are maps where:<ul>
+     *     <li>Keys are XML names of properties (e.g. {@code "checkPoint"})</li>
+     *     <li>Values are either:<ul>
+     *       <li>Namespace URI if {@link #isNamespace(String)} returns {@code true} for that value.</li>
+     *       <li>New name of the element otherwise. In such case, the map must be queried again with
+     *           that new name for obtaining the namespace.</li>
+     *     </ul></li>
+     *   </ul></li>
+     * </ul>
+     *
+     * This map is initialized only once and should not be modified after that point.
+     */
+    private static final Map<String, Map<String,String>> NAMESPACES = load(FILENAME);
+
+    /**
      * 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.
@@ -486,6 +514,7 @@ final class TransformingWriter extends T
     @Override
     public void close() throws XMLStreamException {
         uniqueNamespaces.clear();
+        super.close();
         out.close();
     }
 }