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