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 2022/11/25 17:56:48 UTC

[sis] branch geoapi-4.0 updated: Clarify the name of `Record` and `RecordType` objects created at XML unmarshalling time. The previous "CharacterSequence" name was misleading because it suggested a field name, while it was used for a record containing a single field of type text. The "Single text" name is a better description.

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new ce4e984838 Clarify the name of `Record` and `RecordType` objects created at XML unmarshalling time. The previous "CharacterSequence" name was misleading because it suggested a field name, while it was used for a record containing a single field of type text. The "Single text" name is a better description.
ce4e984838 is described below

commit ce4e9848385a3db75ca5fd0ae02aaf05cff8b88b
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Nov 25 18:54:05 2022 +0100

    Clarify the name of `Record` and `RecordType` objects created at XML unmarshalling time.
    The previous "CharacterSequence" name was misleading because it suggested a field name,
    while it was used for a record containing a single field of type text.
    The "Single text" name is a better description.
    
    https://issues.apache.org/jira/browse/SIS-419
---
 .../sis/internal/metadata/RecordSchemaSIS.java     |  36 +++++--
 .../apache/sis/internal/metadata/Resources.java    |  15 +++
 .../sis/internal/metadata/Resources.properties     |   3 +
 .../sis/internal/metadata/Resources_fr.properties  |   3 +
 .../org/apache/sis/util/iso/DefaultMemberName.java |   2 +-
 .../apache/sis/util/iso/DefaultRecordSchema.java   |  22 +++-
 .../org/apache/sis/util/iso/DefaultRecordType.java |  13 ++-
 .../main/java/org/apache/sis/util/iso/Names.java   |  14 +++
 .../iso/quality/DefaultQuantitativeResultTest.java | 116 +++++++++++++++++++++
 9 files changed, 205 insertions(+), 19 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/RecordSchemaSIS.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/RecordSchemaSIS.java
index 7526e4866b..edf4a8c5cc 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/RecordSchemaSIS.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/RecordSchemaSIS.java
@@ -19,6 +19,7 @@ package org.apache.sis.internal.metadata;
 import java.io.Serializable;
 import java.io.ObjectStreamException;
 import java.util.Collections;
+import org.opengis.util.TypeName;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.util.iso.DefaultRecordType;
@@ -30,7 +31,7 @@ import org.apache.sis.util.resources.Vocabulary;
  * The system-wide schema in the "SIS" namespace.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.3
  * @since   0.7
  * @module
  */
@@ -42,18 +43,28 @@ public final class RecordSchemaSIS extends DefaultRecordSchema implements Serial
     public static final DefaultRecordSchema INSTANCE = new RecordSchemaSIS();
 
     /**
-     * The type of record instances for holding a {@link String} value.
+     * The type name for a record having an unknown number of fields.
+     * This is used at {@code <gco:RecordType>} unmarshalling time,
+     * where the type is not well defined, by assuming one field per line.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-419">SIS-419</a>
+     */
+    public static final TypeName MULTILINE = INSTANCE.createRecordTypeName(
+            Resources.formatInternational(Resources.Keys.MultilineRecord));
+
+    /**
+     * The type of record instances for holding a single {@link String} value.
      */
     public static final DefaultRecordType STRING;
 
     /**
-     * The type of record instances for holding a {@link Double} value.
+     * The type of record instances for holding a single {@link Double} value.
      */
     public static final DefaultRecordType REAL;
     static {
-        final InternationalString label = Vocabulary.formatInternational(Vocabulary.Keys.Value);
-        STRING = (DefaultRecordType) INSTANCE.createRecordType("CharacterSequence", Collections.singletonMap(label, String.class));
-        REAL   = (DefaultRecordType) INSTANCE.createRecordType("Real",              Collections.singletonMap(label, Double.class));
+        final InternationalString field = Vocabulary.formatInternational(Vocabulary.Keys.Value);
+        STRING = singleton(Resources.Keys.SingleText,   field, String.class);
+        REAL   = singleton(Resources.Keys.SingleNumber, field, Double.class);
     }
 
     /**
@@ -63,6 +74,19 @@ public final class RecordSchemaSIS extends DefaultRecordSchema implements Serial
         super(null, null, Constants.SIS);
     }
 
+    /**
+     * Creates a new record type of the given name, which will contain the given field.
+     *
+     * @param  typeName    the record type name as a {@link Resources.Keys} code.
+     * @param  field       the name of the singleton record field.
+     * @param  valueClass  the expected value type for the singleton field.
+     * @return a record type of the given name and field.
+     */
+    private static DefaultRecordType singleton(final short typeName, final InternationalString field, final Class<?> valueClass) {
+        return (DefaultRecordType) INSTANCE.createRecordType(
+                Resources.formatInternational(typeName), Collections.singletonMap(field, valueClass));
+    }
+
     /**
      * On serialization, returns a proxy which will be resolved as {@link #INSTANCE} on deserialization.
      *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java
index 017b2c491f..1ce3c5c303 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.java
@@ -83,6 +83,21 @@ public final class Resources extends IndexedResourceBundle {
          */
         public static final short ExpectedInterface_2 = 5;
 
+        /**
+         * Enregistrement multilignes
+         */
+        public static final short MultilineRecord = 9;
+
+        /**
+         * Single number
+         */
+        public static final short SingleNumber = 8;
+
+        /**
+         * Single text
+         */
+        public static final short SingleText = 7;
+
         /**
          * This metadata is not modifiable.
          */
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.properties b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.properties
index 8ab6976ce6..6eaaf71b73 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.properties
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources.properties
@@ -24,4 +24,7 @@ ConnectionAlreadyInitialized_1    = Connection to \u201c{0}\u201d database is al
 ElementAlreadyInitialized_1       = This metadata element is already initialized with value \u201c{0}\u201d.
 ElementsOmitted_1                 = \u2026 {0} elements omitted \u2026
 ExpectedInterface_2               = Illegal class `{1}`. Specify the `{0}` interface instead.
+MultilineRecord                   = Enregistrement multilignes
+SingleNumber                      = Single number
+SingleText                        = Single text
 UnmodifiableMetadata              = This metadata is not modifiable.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources_fr.properties b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources_fr.properties
index 8359aaa4d5..b1e7ea2494 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources_fr.properties
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Resources_fr.properties
@@ -29,4 +29,7 @@ ConnectionAlreadyInitialized_1    = La connexion \u00e0 la base de donn\u00e9es
 ElementAlreadyInitialized_1       = Cet \u00e9l\u00e9ment de m\u00e9ta-donn\u00e9e est d\u00e9j\u00e0 initialis\u00e9 avec la valeur \u00ab\u202f{0}\u202f\u00bb.
 ElementsOmitted_1                 = \u2026 {0} \u00e9l\u00e9ments omis \u2026
 ExpectedInterface_2               = La classe `{1}` est ill\u00e9gale. Sp\u00e9cifiez l\u2019interface `{0}` \u00e0 la place.
+MultilineRecord                   = Multiline record
+SingleNumber                      = Nombre seul
+SingleText                        = Texte seul
 UnmodifiableMetadata              = Cette m\u00e9ta-donn\u00e9e n\u2019est pas modifiable.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultMemberName.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultMemberName.java
index f647a4b7b7..978d96215e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultMemberName.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultMemberName.java
@@ -149,7 +149,7 @@ public class DefaultMemberName extends DefaultLocalName implements MemberName {
     //////////////////////////////////////////////////////////////////////////////////////////////////
 
     /**
-     * Empty constructor to be used by JAXB only. Despite its `final` declaration,
+     * Empty constructor to be used by JAXB only. Despite its {@code final} declaration,
      * the {@link #attributeType} field will be set by JAXB during unmarshalling.
      */
     private DefaultMemberName() {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
index 6fe3407151..fe5233b3b7 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
@@ -71,7 +71,7 @@ import org.apache.sis.internal.util.Strings;
  * {@link java.io.Serializable} interface) returning a system-wide static constant for their schema.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.3
  *
  * @see DefaultRecordType
  * @see DefaultRecord
@@ -148,11 +148,24 @@ public class DefaultRecordSchema implements RecordSchema {
         return namespace.name().tip();
     }
 
+    /**
+     * Creates the name of a record.
+     *
+     * @param  typeName  name of the record type to create.
+     * @return name of a record type.
+     *
+     * @since 1.3
+     */
+    public TypeName createRecordTypeName(final CharSequence typeName) {
+        ArgumentChecks.ensureNonNull("typeName", typeName);
+        return nameFactory.createTypeName(namespace, typeName, null);
+    }
+
     /**
      * Creates a new record type of the given name, which will contain the given fields.
      * Fields are declared in iteration order.
      *
-     * @param  typeName  the record type name.
+     * @param  typeName  name of the record type to create.
      * @param  fields    the name of each record field, together with the expected value types.
      * @return a record type of the given name and fields.
      * @throws IllegalArgumentException if a record already exists for the given name but with different fields.
@@ -160,9 +173,8 @@ public class DefaultRecordSchema implements RecordSchema {
     public RecordType createRecordType(final CharSequence typeName, final Map<CharSequence,Class<?>> fields)
             throws IllegalArgumentException
     {
-        ArgumentChecks.ensureNonNull("typeName", typeName);
-        ArgumentChecks.ensureNonNull("fields",   fields);
-        final TypeName name = nameFactory.createTypeName(namespace, typeName);
+        ArgumentChecks.ensureNonNull("fields", fields);
+        final TypeName name = createRecordTypeName(typeName);
         final Map<CharSequence,Type> fieldTypes = ObjectConverters.derivedValues(fields, CharSequence.class, toTypes);
         RecordType record;
         synchronized (description) {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
index a519a44dac..087364b7a8 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
@@ -89,7 +89,7 @@ import org.apache.sis.internal.metadata.RecordSchemaSIS;
  * so users wanting serialization may need to provide their own schema.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  *
  * @see DefaultRecord
  * @see DefaultRecordSchema
@@ -452,14 +452,13 @@ public class DefaultRecordType extends RecordDefinition implements RecordType, S
     //////////////////////////////////////////////////////////////////////////////////////////////////
 
     /**
-     * Constructs an initially empty type describing exactly one value as a string.
+     * Constructs an initially empty type describing one field per line.
      * See {@link #setValue(String)} for a description of the supported XML content.
      */
     @SuppressWarnings("unused")
     private DefaultRecordType() {
-        final DefaultRecordType type = RecordSchemaSIS.STRING;
-        typeName  = type.typeName;
-        container = type.container;
+        typeName  = RecordSchemaSIS.MULTILINE;
+        container = RecordSchemaSIS.STRING.container;
     }
 
     /**
@@ -476,7 +475,7 @@ public class DefaultRecordType extends RecordDefinition implements RecordType, S
     }
 
     /**
-     * Sets the record type value as a string. Current implementation expect one field per line.
+     * Sets the record type value as a string. Current implementation expects one field per line.
      * A record can be anything, but usages that we have seen so far write a character sequence
      * of what seems <var>key</var>-<var>description</var> pairs. Examples:
      *
@@ -498,7 +497,7 @@ public class DefaultRecordType extends RecordDefinition implements RecordType, S
                     element = element.subSequence(0, CharSequences.skipTrailingWhitespaces(element, 0, s));
                     // TODO: the part after ":" is the description. For now, we have no room for storing it.
                 }
-                final MemberName m = Names.createMemberName(null, null, element, String.class);
+                final MemberName m = Names.createMemberName(typeName, element, String.class);
                 fields.put(m, RecordSchemaSIS.INSTANCE.toAttributeType(String.class));
             }
             fieldTypes = computeTransientFields(fields);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
index 342a5f7011..7429297cbe 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
@@ -270,6 +270,20 @@ public final class Names extends Static {
         return factory.toTypeName(valueClass);    // SIS-specific method.
     }
 
+    /**
+     * Creates a member name for a record of the given name.
+     * The given namespace is usually an instance of {@link TypeName}.
+     *
+     * @param  namespace  the name of the record which will contain this member name.
+     * @param  localPart  the name which is locale in the given namespace.
+     * @param  valueClass the type of values, used for inferring a {@link TypeName} instance.
+     * @return a member name in the given namespace for values of the given type.
+     */
+    static MemberName createMemberName(final GenericName namespace, final CharSequence localPart, final Class<?> valueClass) {
+        final DefaultNameFactory factory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
+        return factory.createMemberName(factory.createNameSpace(namespace, null), localPart, factory.toTypeName(valueClass));
+    }
+
     /**
      * Creates a member name for values of the given class. A {@link TypeName} will be inferred
      * from the given {@code valueClass} as documented in the {@link DefaultTypeName} javadoc.
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
index d24c5ebac8..78f21b9fbd 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/DefaultQuantitativeResultTest.java
@@ -16,8 +16,22 @@
  */
 package org.apache.sis.metadata.iso.quality;
 
+import java.util.Map;
+import java.util.Iterator;
+import java.util.Collections;
+import javax.xml.bind.JAXBException;
+import org.opengis.util.Type;
+import org.opengis.util.RecordType;
+import org.opengis.util.MemberName;
+import org.opengis.metadata.quality.Element;
+import org.opengis.metadata.quality.QuantitativeResult;
+import org.apache.sis.internal.metadata.RecordSchemaSIS;
+import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.util.SimpleInternationalString;
+import org.apache.sis.util.iso.DefaultRecord;
+import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.xml.XML;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -27,6 +41,7 @@ import static org.junit.Assert.*;
  * Tests {@link DefaultQuantitativeResult}.
  *
  * @author  Martin Desruisseaux (Geomatys)
+ * @author  Guilhem Legal (Geomatys)
  * @version 1.3
  * @since   1.3
  * @module
@@ -47,4 +62,105 @@ public final strictfp class DefaultQuantitativeResultTest extends TestCase {
         r.setErrorStatistic(new SimpleInternationalString("a description"));
         assertFalse(r.isEmpty());
     }
+
+    /**
+     * Creates a {@code DefaultQuantitativeResult} instance wrapped in an element.
+     * The returned element is as below:
+     *
+     * {@preformat text
+     *   Quantitative attribute accuracy
+     *     ├─Measure
+     *     │   └─Name of measure…………………… Some quality flag
+     *     └─Quantitative result
+     *         ├─Value……………………………………………… The quality is okay
+     *         └─Value record type……………… CharacterSequence
+     * }
+     */
+    @SuppressWarnings("deprecation")
+    private static Element createResultInsideElement() {
+        /*
+         * The `RecordType` constructor invoked at unmarshalling time sets the name
+         * to the hard-coded "Multiline record" string. We need to use the same name.
+         */
+        final RecordType recordType = RecordSchemaSIS.INSTANCE.createRecordType(
+                RecordSchemaSIS.MULTILINE.toInternationalString(),
+                Collections.singletonMap("Result of quality measurement", String.class));
+        /*
+         * The `Record` constructor invoked at unmarshalling time sets the type
+         * to the hard-coded "Single text" value. We need to use the same type.
+         */
+        final RecordType singleText = RecordSchemaSIS.STRING;
+        final DefaultRecord  record = new DefaultRecord(singleText);
+        record.set(TestUtilities.getSingleton(singleText.getMembers()), "The quality is okay");
+        /*
+         * Record type and record value are set independently in two properties.
+         * In current implementation, `record.type` is not equal to `recordType`.
+         */
+        assertNotEquals(recordType, record.getRecordType());        // Actually a limitation, not an intended behavior.
+        final DefaultQuantitativeResult result = new DefaultQuantitativeResult();
+        result.setValues(Collections.singletonList(record));
+        result.setValueType(recordType);
+        /*
+         * Opportunistically test the redirection implemented in deprecated methods.
+         */
+        final DefaultQuantitativeAttributeAccuracy element = new DefaultQuantitativeAttributeAccuracy();
+        element.setNamesOfMeasure(Collections.singleton(new SimpleInternationalString("Some quality flag")));
+        element.setResults(Collections.singleton(result));
+        return element;
+    }
+
+    /**
+     * Tests unmarshalling of an XML element containing result records.
+     *
+     * @throws JAXBException if an error occurred during unmarshalling.
+     */
+    @Test
+    public void testUnmarshallingLegacy() throws JAXBException {
+        final String xml =  // Following XML shall match the object built by `createResultInsideElement()`.
+                "<gmd:DQ_QuantitativeAttributeAccuracy xmlns:gmd=\"" + LegacyNamespaces.GMD + '"'
+                                                   + " xmlns:gco=\"" + LegacyNamespaces.GCO + "\">\n" +
+                "  <gmd:nameOfMeasure>\n" +
+                "    <gco:CharacterString>Some quality flag</gco:CharacterString>\n" +
+                "  </gmd:nameOfMeasure>\n" +
+                "  <gmd:result>\n" +
+                "    <gmd:DQ_QuantitativeResult>\n" +
+                "      <gmd:value>\n" +
+                "        <gco:Record>The quality is okay</gco:Record>\n" +
+                "      </gmd:value>\n" +
+                "      <gmd:valueType>\n" +
+                "        <gco:RecordType>Result of quality measurement</gco:RecordType>\n" +
+                "      </gmd:valueType>\n" +
+                "    </gmd:DQ_QuantitativeResult>\n" +
+                "  </gmd:result>\n" +
+                "</gmd:DQ_QuantitativeAttributeAccuracy>";
+
+        final Element unmarshalled = (Element) XML.unmarshal(xml);
+        final Element programmatic = createResultInsideElement();
+        /*
+         * Before to compare the two `Element`, compare some individual components.
+         * The intent is to identify which metadata is not equal in case of test failure.
+         */
+        final QuantitativeResult   uResult = (QuantitativeResult) TestUtilities.getSingleton(unmarshalled.getResults());
+        final QuantitativeResult   pResult = (QuantitativeResult) TestUtilities.getSingleton(programmatic.getResults());
+        final RecordType           uType   = uResult.getValueType();
+        final RecordType           pType   = pResult.getValueType();
+        final Map<MemberName,Type> uFields = uType.getFieldTypes();
+        final Map<MemberName,Type> pFields = pType.getFieldTypes();
+        final Iterator<MemberName> uIter   = uFields.keySet().iterator();
+        final Iterator<MemberName> pIter   = pFields.keySet().iterator();
+        assertEquals(uFields.size(), pFields.size());
+        while (uIter.hasNext() | pIter.hasNext()) {
+            final MemberName uName = uIter.next();
+            final MemberName pName = pIter.next();
+            assertEquals(uName.scope(), pName.scope());
+            assertEquals(uName, pName);
+            assertEquals(uFields.get(uName), pFields.get(pName));
+        }
+        assertEquals(uFields,             pFields);
+        assertEquals(uType.getMembers(),  pType.getMembers());
+        assertEquals(uType.getTypeName(), pType.getTypeName());
+        assertEquals(uType,               pType);
+        assertEquals(uResult,             pResult);
+        assertEquals(unmarshalled, programmatic);
+    }
 }