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