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 2023/11/18 17:05:40 UTC
(sis) branch geoapi-4.0 updated: Add support for nil date and nil URI in metadata.
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 3d379f9aad Add support for nil date and nil URI in metadata.
3d379f9aad is described below
commit 3d379f9aad63817f4b728bdcc76775126328245e
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Nov 18 17:50:51 2023 +0100
Add support for nil date and nil URI in metadata.
---
.../main/org/apache/sis/xml/NilDate.java | 69 +++++++++++++++++
.../org/apache/sis/xml/NilInternationalString.java | 2 +-
.../main/org/apache/sis/xml/NilReason.java | 47 ++++++------
.../apache/sis/xml/bind/FinalClassExtensions.java | 2 +-
.../main/org/apache/sis/xml/package-info.java | 2 +-
.../test/org/apache/sis/xml/NilReasonTest.java | 88 ++++++++++++++--------
6 files changed, 149 insertions(+), 61 deletions(-)
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilDate.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilDate.java
new file mode 100644
index 0000000000..047603685f
--- /dev/null
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilDate.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.xml;
+
+import java.util.Date;
+import java.io.ObjectStreamException;
+
+
+/**
+ * An empty {@code Date} which is nil for the given reason.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+final class NilDate extends Date implements NilObject {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 4374532826187673813L;
+
+ /**
+ * The reason why the object is nil.
+ */
+ private final NilReason reason;
+
+ /**
+ * Creates a new international string which is nil for the given reason.
+ */
+ NilDate(final NilReason reason) {
+ super(0);
+ this.reason = reason;
+ }
+
+ /**
+ * Returns the reason why this object is nil.
+ */
+ @Override
+ public NilReason getNilReason() {
+ return reason;
+ }
+
+ /**
+ * Unconditionally returns en empty string.
+ */
+ @Override
+ public String toString() {
+ return "";
+ }
+
+ /**
+ * Invoked on deserialization for replacing the deserialized instance by the unique instance.
+ */
+ private Object readResolve() throws ObjectStreamException {
+ return reason.createNilObject(Date.class);
+ }
+}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilInternationalString.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilInternationalString.java
index d216921f5f..b902732366 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilInternationalString.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilInternationalString.java
@@ -24,7 +24,7 @@ import org.apache.sis.util.resources.Errors;
/**
- * An empty {@link InternationalString} which is nil for the given reason.
+ * An empty {@code InternationalString} which is nil for the given reason.
*
* @author Martin Desruisseaux (Geomatys)
*/
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilReason.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilReason.java
index c701f94242..25310a03be 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilReason.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/NilReason.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.xml;
+import java.util.Date;
import java.util.Map;
import java.net.URI;
import java.net.URISyntaxException;
@@ -55,7 +56,7 @@ import org.apache.sis.math.MathFunctions;
* This final class is immutable and thus inherently thread-safe.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
*
* @see NilObject
*
@@ -427,7 +428,7 @@ public final class NilReason implements Serializable {
* {@code 0} or {@code false}, in this preference order, depending on the method return type.</li>
* </ul>
* </li>
- * <li>One of {@link Float}, {@link Double} or {@link String} types.
+ * <li>One of {@link Float}, {@link Double}, {@link String}, {@link URI} or {@link Date} types.
* In such case, this method returns an instance which will be recognized as "nil" by the XML marshaller.</li>
* </ul>
*
@@ -445,25 +446,21 @@ public final class NilReason implements Serializable {
* @return an {@link NilObject} of the given type.
*/
@SuppressWarnings("unchecked")
- public synchronized <T> T createNilObject(final Class<T> type) {
+ public <T> T createNilObject(final Class<T> type) {
ArgumentChecks.ensureNonNull("type", type);
/*
* Check for existing instance in the cache before to create a new object. Returning a unique
* instance is mandatory for the types handled by `createNilInstance(Class)`. Since we have
* to cache those values anyway, we opportunistically extend the caching to other types too.
- *
- * Implementation note: we have two synchronizations here: one lock on `this` because of the
- * `synchronized` statement in this method signature, and another lock in `WeakValueHashMap`.
- * The second lock may seem useless since we already hold a lock on `this`. But it is actually
- * needed because the garbage-collected entries are removed from the map in a background thread
- * (see ReferenceQueueConsumer), which is synchronized on the map itself. It is better to keep
- * the synchronization on the map shorter than the snychronization on `this` because a single
- * ReferenceQueueConsumer thread is shared by all the SIS library.
*/
- if (nilObjects == null) {
- nilObjects = new WeakValueHashMap<>((Class) Class.class);
+ Map<Class<?>, Object> instances;
+ synchronized (this) {
+ instances = nilObjects;
+ if (instances == null) {
+ nilObjects = instances = new WeakValueHashMap<>((Class) Class.class);
+ }
}
- Object object = nilObjects.get(type);
+ Object object = instances.get(type);
if (object == null) {
/*
* If no object has been previously created, check for the usual case where the requested type
@@ -490,9 +487,8 @@ public final class NilReason implements Serializable {
*/
object = createNilInstance(type);
}
- if (nilObjects.put(type, object) != null) {
- throw new AssertionError(type); // Should never happen.
- }
+ final Object current = instances.putIfAbsent(type, object);
+ if (current != null) object = current;
}
return type.cast(object);
}
@@ -503,12 +499,6 @@ public final class NilReason implements Serializable {
* <p><b>Reminder:</b> If more special cases are added, do not forget to update the {@link #forObject(Object)}
* method and to update the {@link #createNilObject(Class)} and {@link #forObject(Object)} javadoc.</p>
*
- * <h4>Implementation note</h4>
- * There is no special case for {@link Character} because Java {@code char}s are not really full Unicode characters.
- * They are parts of UTF-16 encoding instead. If there is a need to represent a single Unicode character, we should
- * probably still use a {@link String} where the string contain 1 or 2 Java characters. This may also facilitate the
- * encoding in the XML files, since many files use another encoding than UTF-16 anyway.
- *
* @throws IllegalArgumentException if the given type is not a supported type.
*
* @see <a href="https://issues.apache.org/jira/browse/SIS-586">SIS-586</a>
@@ -519,7 +509,11 @@ public final class NilReason implements Serializable {
if (type == Float .class) return Float .valueOf(MathFunctions.toNanFloat(ordinal()));
final Object object;
if (type == String .class) {
- object = new String(""); // REALLY need a new instance.
+ object = new String(""); // REALLY need a new instance.
+ } else if (type == URI.class) {
+ object = URI.create(""); // Really need a new instance.
+ } else if (type == Date.class) {
+ object = new NilDate(this);
} else {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "type", type));
}
@@ -534,7 +528,8 @@ public final class NilReason implements Serializable {
* <ul>
* <li>If the given object implements the {@link NilObject} interface, then this method delegates
* to the {@link NilObject#getNilReason()} method.</li>
- * <li>Otherwise if the given object is one of the {@link Float}, {@link Double} or {@link String} instances
+ * <li>Otherwise if the given object is one of the {@link Float}, {@link Double}, {@link String},
+ * {@link URI} or {@link Date} instances
* returned by {@link #createNilObject(Class)}, then this method returns the associated reason.</li>
* <li>Otherwise this method returns {@code null}.</li>
* </ul>
@@ -560,7 +555,7 @@ public final class NilReason implements Serializable {
if (value.isNaN()) {
return forNumber(value);
}
- } else if (object instanceof String) {
+ } else if (object instanceof String || object instanceof URI) {
return (NilReason) FinalClassExtensions.property(object);
}
}
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/FinalClassExtensions.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/FinalClassExtensions.java
index ada8d9a7e3..6631c07c58 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/FinalClassExtensions.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/FinalClassExtensions.java
@@ -58,7 +58,7 @@ public final class FinalClassExtensions {
* does not provides any map implementation which is both an {@code IdentityHashMap} and a {@code WeakHashMap}.
*
* For now we do not use weak references. This means that if a user creates a custom {@code NilReason} by a call
- * to {@link NilReason#valueOf(String)} and if (s)he uses that nil reason for a primitive type, then that custom
+ * to {@link NilReason#valueOf(String)} and if (s)he uses that nil reason for a final class, then that custom
* {@code NilReason} instance and its sentinel values will never be garbage-collected.
* We presume that such cases will be rare enough for not being an issue in practice.
*
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/package-info.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/package-info.java
index 2d575a1536..0af032a3d2 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/package-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/package-info.java
@@ -59,7 +59,7 @@
* @author Guilhem Legal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @author Cullen Rombach (Image Matters)
- * @version 1.4
+ * @version 1.5
* @since 0.3
*/
package org.apache.sis.xml;
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/NilReasonTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/NilReasonTest.java
index 1ac8564526..513ded6721 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/NilReasonTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/NilReasonTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.sis.xml;
+import java.net.URI;
+import java.util.Date;
import java.net.URISyntaxException;
import org.opengis.util.InternationalString;
import org.opengis.metadata.citation.Citation;
@@ -27,8 +29,7 @@ import org.apache.sis.util.ArraysExt;
import org.junit.Test;
import org.apache.sis.test.TestCase;
-import static org.junit.Assert.*;
-import static org.opengis.test.Assert.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.*;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.metadata.citation.Responsibility;
@@ -85,10 +86,10 @@ public final class NilReasonTest extends TestCase {
assertSame(NilReason.OTHER, NilReason.valueOf("other"));
final NilReason other = NilReason.valueOf("other:myReason");
assertSame(other, NilReason.valueOf(" OTHER : myReason "));
- assertNotSame("Expected a new instance.", NilReason.OTHER, other);
- assertFalse ("NilReason.equals(Object)", NilReason.OTHER.equals(other));
- assertEquals ("NilReason.getOtherExplanation()", "myReason", other.getOtherExplanation());
- assertNull ("NilReason.getURI()", other.getURI());
+ assertNotSame (NilReason.OTHER, other, "Expected a new instance.");
+ assertNotEquals(NilReason.OTHER, other);
+ assertEquals("myReason", other.getOtherExplanation());
+ assertNull(other.getURI(), "NilReason.getURI()");
final NilReason[] reasons = NilReason.values();
assertTrue(ArraysExt.contains(reasons, NilReason.TEMPLATE));
@@ -105,8 +106,8 @@ public final class NilReasonTest extends TestCase {
public void testValueOfURI() throws URISyntaxException {
final NilReason other = NilReason.valueOf("http://www.nilreasons.org");
assertSame(other, NilReason.valueOf(" http://www.nilreasons.org "));
- assertNull ("NilReason.getOtherExplanation()", other.getOtherExplanation());
- assertEquals("NilReason.getURI()", "http://www.nilreasons.org", String.valueOf(other.getURI()));
+ assertNull(other.getOtherExplanation());
+ assertEquals("http://www.nilreasons.org", String.valueOf(other.getURI()));
final NilReason[] reasons = NilReason.values();
assertTrue(ArraysExt.contains(reasons, NilReason.TEMPLATE));
@@ -124,10 +125,10 @@ public final class NilReasonTest extends TestCase {
final Float value = NilReason.MISSING.createNilObject(Float.class);
assertEquals (nan, value);
assertNotSame(nan, value);
- assertSame("NilReason.forObject(…)", NilReason.MISSING, NilReason.forObject(value));
- assertNull("NilReason.forObject(…)", NilReason.forObject(nan));
- assertNull("NilReason.forObject(…)", NilReason.forObject(0f));
- assertSame("Expected cached value.", value, NilReason.MISSING.createNilObject(Float.class));
+ assertSame(NilReason.MISSING, NilReason.forObject(value));
+ assertNull(NilReason.forObject(nan));
+ assertNull(NilReason.forObject(0f));
+ assertSame(value, NilReason.MISSING.createNilObject(Float.class), "Expected cached value.");
}
/**
@@ -140,10 +141,10 @@ public final class NilReasonTest extends TestCase {
final Double value = NilReason.TEMPLATE.createNilObject(Double.class);
assertEquals (nan, value);
assertNotSame(nan, value);
- assertSame("NilReason.forObject(…)", NilReason.TEMPLATE, NilReason.forObject(value));
- assertNull("NilReason.forObject(…)", NilReason.forObject(nan));
- assertNull("NilReason.forObject(…)", NilReason.forObject(0.0));
- assertSame("Expected cached value.", value, NilReason.TEMPLATE.createNilObject(Double.class));
+ assertSame(NilReason.TEMPLATE, NilReason.forObject(value));
+ assertNull(NilReason.forObject(nan));
+ assertNull(NilReason.forObject(0.0));
+ assertSame(value, NilReason.TEMPLATE.createNilObject(Double.class), "Expected cached value.");
}
/**
@@ -155,10 +156,10 @@ public final class NilReasonTest extends TestCase {
final String value = NilReason.MISSING.createNilObject(String.class);
assertEquals ("", value);
assertNotSame("", value);
- assertSame("NilReason.forObject(…)", NilReason.MISSING, NilReason.forObject(value));
- assertNull("NilReason.forObject(…)", NilReason.forObject(""));
- assertNull("NilReason.forObject(…)", NilReason.forObject("null"));
- assertSame("Expected cached value.", value, NilReason.MISSING.createNilObject(String.class));
+ assertSame(NilReason.MISSING, NilReason.forObject(value));
+ assertNull(NilReason.forObject(""));
+ assertNull(NilReason.forObject("null"));
+ assertSame(value, NilReason.MISSING.createNilObject(String.class), "Expected cached value.");
}
/**
@@ -169,9 +170,32 @@ public final class NilReasonTest extends TestCase {
public void testCreateNilInternationalString() {
final InternationalString value = NilReason.MISSING.createNilObject(InternationalString.class);
assertEquals("", value.toString());
- assertInstanceOf("Unexpected impl.", NilObject.class, value);
- assertSame("NilReason.forObject(…)", NilReason.MISSING, NilReason.forObject(value));
- assertSame("Expected cached value.", value, NilReason.MISSING.createNilObject(InternationalString.class));
+ assertInstanceOf(NilObject.class, value);
+ assertSame(NilReason.MISSING, NilReason.forObject(value));
+ assertSame(value, NilReason.MISSING.createNilObject(InternationalString.class), "Expected cached value.");
+ }
+
+ /**
+ * Tests {@link NilReason#createNilObject(Class)} for a date.
+ */
+ @Test
+ public void testCreateNilDate() {
+ final Date value = NilReason.TEMPLATE.createNilObject(Date.class);
+ assertEquals("", value.toString());
+ assertInstanceOf(NilObject.class, value);
+ assertSame(NilReason.TEMPLATE, NilReason.forObject(value));
+ assertSame(value, NilReason.TEMPLATE.createNilObject(Date.class), "Expected cached value.");
+ }
+
+ /**
+ * Tests {@link NilReason#createNilObject(Class)} for an URI.
+ */
+ @Test
+ public void testCreateNilURI() {
+ final URI value = NilReason.MISSING.createNilObject(URI.class);
+ assertEquals("", value.toString());
+ assertSame(NilReason.MISSING, NilReason.forObject(value));
+ assertSame(value, NilReason.MISSING.createNilObject(URI.class), "Expected cached value.");
}
/**
@@ -180,12 +204,12 @@ public final class NilReasonTest extends TestCase {
@Test
public void testCreateNilObject() {
final Citation citation = NilReason.TEMPLATE.createNilObject(Citation.class);
- assertInstanceOf("Unexpected proxy.", NilObject.class, citation);
+ assertInstanceOf(NilObject.class, citation);
assertNull(citation.getTitle());
assertTrue(citation.getDates().isEmpty());
- assertEquals("NilObject.toString()", "Citation[template]", citation.toString());
- assertSame("NilReason.forObject(…)", NilReason.TEMPLATE, NilReason.forObject(citation));
- assertSame("Expected cached value.", citation, NilReason.TEMPLATE.createNilObject(Citation.class));
+ assertEquals("Citation[template]", citation.toString());
+ assertSame(NilReason.TEMPLATE, NilReason.forObject(citation));
+ assertSame(citation, NilReason.TEMPLATE.createNilObject(Citation.class), "Expected cached value.");
}
/**
@@ -196,12 +220,12 @@ public final class NilReasonTest extends TestCase {
final Citation e1 = NilReason.TEMPLATE.createNilObject(Citation.class);
final Citation e2 = NilReason.MISSING .createNilObject(Citation.class);
final Citation e3 = NilReason.TEMPLATE.createNilObject(Citation.class);
- assertEquals("NilObject.hashCode()", e1.hashCode(), e3.hashCode());
- assertFalse ("NilObject.hashCode()", e1.hashCode() == e2.hashCode());
- assertEquals("NilObject.equals(Object)", e1, e3);
- assertFalse ("NilObject.equals(Object)", e1.equals(e2));
+ assertEquals(e1.hashCode(), e3.hashCode());
+ assertFalse (e1.hashCode() == e2.hashCode());
+ assertEquals(e1, e3);
+ assertFalse (e1.equals(e2));
- assertInstanceOf("e1", LenientComparable.class, e1);
+ assertInstanceOf(LenientComparable.class, e1);
final LenientComparable c = (LenientComparable) e1;
assertTrue (c.equals(e3, ComparisonMode.STRICT));
assertFalse(c.equals(e2, ComparisonMode.STRICT));