Author: desruisseaux
Date: Sun Feb 18 15:44:50 2018
New Revision: 1824672
URL: http://svn.apache.org/viewvc?rev=1824672&view=rev
Log:
More simplification of the algorithm for renaming XML element.
Move the 'convert(QName)' method to the Transformer parent class.
Modified:
sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/xml/NamespaceContent.java
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-metadata/src/test/java/org/apache/sis/xml/NamespaceContent.java
URL:
http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/xml/NamespaceContent.java?rev=1824672&r1=1824671&r2=1824672&view=diff
==============================================================================
---
sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/xml/NamespaceContent.java
[UTF-8] (original)
+++
sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/xml/NamespaceContent.java
[UTF-8] Sun Feb 18 15:44:50 2018
@@ -136,7 +136,7 @@ public final class NamespaceContent {
*/
final String topLevelTypeName = root.name();
String classNS = namespace(classe, root.namespace());
- add(classNS, topLevelTypeName, TransformingReader.TYPE_KEY);
+ add(classNS, topLevelTypeName, Transformer.TYPE_KEY);
for (;; classNS = namespace(classe, root.namespace())) {
for (final Method method : classe.getDeclaredMethods()) {
if (!method.isBridge()) {
@@ -170,7 +170,7 @@ public final class NamespaceContent {
}
}
if (singleton != null) {
- add(namespace(classe, singleton.namespace()),
singleton.name(), TransformingReader.TYPE_KEY);
+ add(namespace(classe, singleton.namespace()),
singleton.name(), Transformer.TYPE_KEY);
}
}
}
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=1824672&r1=1824671&r2=1824672&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] Sun Feb 18 15:44:50 2018
@@ -96,37 +96,52 @@ abstract class Transformer {
private static final char RENAME_SEPARATOR = '/';
/**
+ * A sentinel value in files loaded by {@link #load(String)} meaning that
the identifier stands for the type
+ * name instead than for a property name. This sentinel value is used in
{@value TransformingReader#FILENAME}
+ * and {@value TransformingWriter#FILENAME} resource files. For example in
the following:
+ *
+ * {@preformat text
+ * http://standards.iso.org/iso/19115/-3/cit/1.0
+ * CI_Citation
+ * <type>
+ * title
+ * alternateTitle
+ * }
+ *
+ * The {@code <type>} sentinel value is substituted by {@code
CI_Citation}, which means that the {@code Citation}
+ * class itself is in the {@code cit} namespace. This needs to be
specified explicitely because the properties in
+ * a class are not necessarily in the same namespace than the enclosing
class.
+ */
+ static final String TYPE_KEY = "<type>";
+
+ /**
* The external XML format version to (un)marshal from.
*/
final TransformVersion version;
/**
- * The outer element name together with its attribute. This is an element
in a linked list.
+ * List of encountered XML tags, 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. For example given the following XML, this list should
contains {@code cit:CI_Citation},
+ * {@code cit:date} and {@code cit:CI_Date} (in that order) when the
(un)marshalling reaches the "…" location.
+ *
+ * {@preformat xml
+ * <cit:CI_Citation>
+ * <cit:date>
+ * <cit:CI_Date>
+ * …
+ * </cit:CI_Date>
+ * </cit:date>
+ * </cit:CI_Citation>
+ * }
*/
- 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;
- }
- }
+ private final List<QName> outerElements;
/**
- * 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.
+ * Properties of the last outer elements, or {@code null} if not yet
determined.
+ * If non-empty, this is one of the values got from the map given in
argument to {@link #open(QName, Map)}.
*/
- private OuterElement outerElements;
+ private Map<String,String> outerElementProperties;
/**
* Temporary list of attributes after their namespace change.
@@ -138,8 +153,22 @@ abstract class Transformer {
* Creates a new XML reader or writer.
*/
Transformer(final TransformVersion version) {
- this.version = version;
- renamedAttributes = new ArrayList<>();
+ this.version = version;
+ outerElements = new ArrayList<>();
+ renamedAttributes = new ArrayList<>();
+ outerElementProperties = Collections.emptyMap();
+ }
+
+ /**
+ * Removes the trailing slash in given URI, if any. It is caller's
responsibility
+ * to ensure that the URI is not null and not empty before to invoke this
method.
+ */
+ static String removeTrailingSlash(String uri) {
+ final int end = uri.length() - 1;
+ if (uri.charAt(end) == '/') {
+ uri = uri.substring(0, end);
+ }
+ return uri;
}
/**
@@ -188,6 +217,7 @@ abstract class Transformer {
{
Map<String,String> attributes = null; // All
attributes for a given type.
String namespace = null; // Value to
store in 'attributes' map.
+ String type = null; // Class or
code list containing attributes.
String line;
while ((line = in.readLine()) != null) {
final int length = line.length();
@@ -199,10 +229,12 @@ abstract class Transformer {
if (!isNamespace(element)) break;
// Report illegal format.
namespace = element.intern();
attributes = null;
+ type = null;
continue;
}
case 1: {
// New type in above namespace URI.
- attributes = m.computeIfAbsent(element.intern(),
(k) -> new HashMap<>());
+ type = element.intern();
+ attributes = m.computeIfAbsent(type, (k) -> new
HashMap<>());
continue;
}
case 2: {
// New attribute in above type.
@@ -213,7 +245,7 @@ abstract class Transformer {
element =
element.substring(s+1).trim().intern();
attributes.put(old, element);
} else {
- element = element.intern();
+ element = element.equals(TYPE_KEY) ? type :
element.intern();
}
attributes.put(element, namespace);
continue;
@@ -235,18 +267,6 @@ abstract class Transformer {
}
/**
- * Removes the trailing slash in given URI, if any. It is caller's
responsibility
- * to ensure that the URI is not null and not empty before to invoke this
method.
- */
- static String removeTrailingSlash(String uri) {
- final int end = uri.length() - 1;
- if (uri.charAt(end) == '/') {
- uri = uri.substring(0, end);
- }
- return uri;
- }
-
- /**
* Returns a snapshot of {@link #renamedAttributes} list and clears the
later.
*/
final List<Attribute> attributes() {
@@ -262,9 +282,9 @@ abstract class Transformer {
}
/**
- * 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}:
+ * Returns {@code true} if an element with the given name is 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>
@@ -277,10 +297,9 @@ abstract class Transformer {
* }
*
* 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.
+ * version depending on new formats to support.
*/
- static boolean isOuterElement(final String localPart) {
+ private static boolean isTypeElement(final String localPart) {
if (localPart.length() < 4) return false;
final char c = localPart.charAt(0);
return (c >= 'A' && c <= 'Z');
@@ -294,9 +313,9 @@ abstract class Transformer {
*/
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()));
+ if (isTypeElement(localPart)) {
+ outerElements.add(name);
+ outerElementProperties = namespaces.getOrDefault(localPart,
Collections.emptyMap());
}
}
@@ -305,34 +324,70 @@ abstract class Transformer {
* 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.
+ * @param name element name as declared in JAXB annotations.
+ * @param namespaces namespaces map loaded by {@link #load(String)}.
*/
- 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.
+ final void close(final QName name, final Map<String, Map<String,String>>
namespaces) {
+ if (isTypeElement(name.getLocalPart())) {
+ outerElementProperties = null;
+ for (int i=outerElements.size(); --i >= 0;) {
+ if (name.equals(outerElements.get(i))) {
+ outerElements.remove(i);
+ final String parent = (--i >= 0) ?
outerElements.get(i).getLocalPart() : null;
+ outerElementProperties = namespaces.getOrDefault(parent,
Collections.emptyMap());
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.
+ * Frees any resources associated with this reader.
*/
- final Map<String,String> attributeNS() {
- return (outerElements != null) ? outerElements.attributeNS :
Collections.emptyMap();
+ public void close() throws XMLStreamException {
+ outerElementProperties = null;
+ outerElements.clear();
}
/**
- * Frees any resources associated with this reader.
+ * Renames en element using the namespaces map give to the {@code open(…)}
and {@code close(…)} methods.
+ * When unmarshalling, this imports a name read from the XML document to
the name to give to JAXB.
+ * When marshalling, this exports a name used in JAXB annotation to the
name to use in XML document.
+ * The new namespace depends on both the old namespace and the element
name.
+ * The prefix is computed by {@link #prefixReplacement(String, String)}.
+ *
+ * @param name the name of the element or attribute currently being
read or written.
+ * @return a name with potentially the namespace and the local part
replaced.
*/
- public void close() throws XMLStreamException {
- outerElements = null;
+ final QName convert(QName name) throws XMLStreamException {
+ String localPart = name.getLocalPart();
+ String namespace = outerElementProperties.get(localPart);
+ if (namespace != null && !isNamespace(namespace)) {
+ localPart = namespace;
+ namespace = outerElementProperties.get(localPart);
+ }
+ /*
+ * If above code found no namespace by looking in our dictionary of
special cases,
+ * maybe there is a simple one-to-one relationship between the old and
new namespaces.
+ * Otherwise we leave the namespace unchanged.
+ */
+ final String oldNS = name.getNamespaceURI();
+ if (namespace == null) {
+ namespace = version.importNS(oldNS);
+ }
+ if (!namespace.equals(oldNS) ||
!localPart.equals(name.getLocalPart())) {
+ name = new QName(namespace, localPart,
prefixReplacement(name.getPrefix(), namespace));
+ }
+ return name;
}
+
+ /**
+ * Returns the prefix to use for a name in a new namespace.
+ *
+ * @param previous the prefix associated to old namespace.
+ * @param namespace the new namespace URI.
+ * @return prefix to use for the new namespace.
+ * @throws XMLStreamException if an error occurred while fetching the
prefix.
+ */
+ abstract String prefixReplacement(String previous, String namespace)
throws XMLStreamException;
}
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=1824672&r1=1824671&r2=1824672&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] Sun Feb 18 15:44:50 2018
@@ -30,6 +30,7 @@ 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.util.collection.BackingStoreException;
import static javax.xml.stream.XMLStreamConstants.*;
@@ -59,12 +60,6 @@ final class TransformingReader extends T
static final String FILENAME = "NamespaceContent.lst";
/**
- * A key in {@link #NAMESPACES} sub-map meaning that the value (a
namespace URI) is for the type instead
- * than for an attribute. Shall be the same string than the one used in
{@value #FILENAME} resource file.
- */
- static final String TYPE_KEY = "<type>";
-
- /**
* The mapping from (<var>type</var>, <var>attribute</var>) pairs to
namespaces.
*
* <ul>
@@ -92,7 +87,7 @@ final class TransformingReader extends T
*/
static String namespace(final String type) {
final Map<String,String> attributes = NAMESPACES.get(type);
- return (attributes != null) ? attributes.get(TYPE_KEY) : null;
+ return (attributes != null) ? attributes.get(type) : null;
}
/**
@@ -105,6 +100,8 @@ final class TransformingReader extends T
* 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.
+ *
+ * @see #prefixReplacement(String, String)
*/
private final Map<String,String> prefixes;
@@ -160,14 +157,11 @@ final class TransformingReader extends T
*/
@Override
public Object next() {
- final XMLEvent event = (XMLEvent) in.next();
- final XMLEvent next = nextEvent;
- if (next != null) {
- nextEvent = null;
- assert isWrapper(event, next) : event;
- return next;
+ try {
+ return nextEvent();
+ } catch (XMLStreamException e) {
+ throw new BackingStoreException(e);
}
- return convert(event);
}
/**
@@ -215,7 +209,7 @@ final class TransformingReader extends T
* @return the converted event (may be the same instance).
*/
@SuppressWarnings("unchecked") // TODO: remove on JDK9
- private XMLEvent convert(XMLEvent event) {
+ private XMLEvent convert(XMLEvent event) throws XMLStreamException {
switch (event.getEventType()) {
case ATTRIBUTE: {
event = convert((Attribute) event);
@@ -255,7 +249,7 @@ final class TransformingReader extends T
if (namespaces != null) {
event = new TransformedEvent.End(e, name, namespaces);
}
- close(originalName); // Must be invoked
only after 'convert(QName)'
+ close(originalName, NAMESPACES); // Must be invoked
only after 'convert(QName)'
break;
}
}
@@ -263,65 +257,10 @@ final class TransformingReader extends T
}
/**
- * Imports a name read from the XML document to the name to give to JAXB.
- * The new namespace depends on both the old namespace and the element
name.
- * The prefix is left unchanged since it can be arbitrary (even if
confusing for
- * human reader used to ISO/TC211 prefixes, it is non-ambiguous to the
computer).
- *
- * @param name the name of the element or attribute currently being
read.
- * @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(final QName name) {
- String namespace; // In this method,
null means no change to given name.
- String localPart = name.getLocalPart();
- /*
- * 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 use the map of
attributes
- * of the parent element.
- */
- attributeNS = attributeNS();
- namespace = attributeNS.get(localPart);
- if (namespace != null && !isNamespace(namespace)) {
- localPart = namespace;
- namespace = attributeNS.get(namespace);
- }
- }
- /*
- * If above code found no namespace by looking in our dictionary of
special cases,
- * maybe there is a simple one-to-one relationship between the old and
new namespaces.
- * Otherwise we leave the namespace unchanged.
- */
- final String oldNS = name.getNamespaceURI();
- 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. 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.
- */
- final String prefix = prefixes.computeIfAbsent(namespace,
- (ns) -> Namespaces.getPreferredPrefix(ns, name.getPrefix()));
- return new QName(namespace, localPart, prefix);
- }
-
- /**
* Imports an attribute read from the XML document.
* If there is no name change, then this method returns the given instance
as-is.
*/
- private Attribute convert(Attribute attribute) {
+ private Attribute convert(Attribute attribute) throws XMLStreamException {
final QName originalName = attribute.getName();
final QName name = convert(originalName);
if (name != originalName) {
@@ -331,6 +270,20 @@ final class TransformingReader extends T
}
/**
+ * Returns the prefix to use for a name in a new namespace. The prefix
should have been specified (indirectly)
+ * by a previous call to {@code importNS(Namespace, …)}, for example as a
result of a {@code NAMESPACE} event.
+ * If not, we compute it now using the same algorithm than in {@code
importNS}.
+ *
+ * @param previous the prefix associated to old namespace.
+ * @param namespace the new namespace URI.
+ * @return prefix to use for the new namespace.
+ */
+ @Override
+ final String prefixReplacement(final String previous, final String
namespace) {
+ return prefixes.computeIfAbsent(namespace, (ns) ->
Namespaces.getPreferredPrefix(ns, previous));
+ }
+
+ /**
* 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"}.
@@ -350,8 +303,7 @@ final class TransformingReader extends T
uri = removeTrailingSlash(uri);
final String imported = uri.equals(oldURI) ? newURI :
version.importNS(uri);
if (imported != uri) {
- final String prefix = prefixes.computeIfAbsent(imported,
- (ns) -> Namespaces.getPreferredPrefix(ns,
namespace.getPrefix()));
+ final String prefix = prefixReplacement(namespace.getPrefix(),
imported);
return new TransformedEvent.NS(namespace, prefix, imported);
}
}
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=1824672&r1=1824671&r2=1824672&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] Sun Feb 18 15:44:50 2018
@@ -188,32 +188,45 @@ final class TransformingWriter extends T
final TransformVersion.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);
+ name = new QName(uri, r.exportProperty(name.getLocalPart()),
prefixReplacement(name.getPrefix(), uri));
}
}
return name;
}
/**
+ * Returns the prefix to use for a name in a new namespace.
+ *
+ * @param previous the prefix associated to old namespace.
+ * @param namespace the new namespace URI.
+ * @return prefix to use for the new namespace.
+ * @throws XMLStreamException if an error occurred while fetching the
prefix.
+ */
+ @Override
+ final String prefixReplacement(final String previous, final String
namespace) throws XMLStreamException {
+ /*
+ * 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(namespace);
+ if (prefix == null) {
+ prefix = Namespaces.getPreferredPrefix(namespace, previous);
+ out.setPrefix(prefix, namespace);
+ /*
+ * 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.
+ */
+ }
+ return prefix;
+ }
+
+ /**
* Returns the attribute to write in the XML document.
* If there is no name change, then this method returns the given instance
as-is.
*/