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();
     }
 }


Reply via email to