sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1508068 - in /sis/branches/JDK7/core/sis-utility/src: main/java/org/apache/sis/xml/NilInternationalString.java main/java/org/apache/sis/xml/NilReason.java test/java/org/apache/sis/xml/NilReasonTest.java
Date Mon, 29 Jul 2013 13:49:08 GMT
Author: desruisseaux
Date: Mon Jul 29 13:49:07 2013
New Revision: 1508068

URL: http://svn.apache.org/r1508068
Log:
Cache the NilObjects created so far, and add support for nil InternationalString.

Added:
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilInternationalString.java
  (with props)
Modified:
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java

Added: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilInternationalString.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilInternationalString.java?rev=1508068&view=auto
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilInternationalString.java
(added)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilInternationalString.java
[UTF-8] Mon Jul 29 13:49:07 2013
@@ -0,0 +1,123 @@
+/*
+ * 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.Locale;
+import java.io.Serializable;
+import java.io.ObjectStreamException;
+import org.opengis.util.InternationalString;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * An empty {@link InternationalString} which is nil for the given reason.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.4
+ * @version 0.4
+ * @module
+ */
+final class NilInternationalString implements InternationalString, NilObject, Serializable
{
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -4275403933320692351L;
+
+    /**
+     * The reason why the object is nil.
+     */
+    private final NilReason reason;
+
+    /**
+     * Creates a new international string which is nil for the given reason.
+     */
+    NilInternationalString(final NilReason reason) {
+        this.reason = reason;
+    }
+
+    /**
+     * Returns the reason why this object is nil.
+     */
+    @Override
+    public NilReason getNilReason() {
+        return reason;
+    }
+
+    /**
+     * Returns the length, which is always 0.
+     */
+    @Override
+    public int length() {
+        return 0;
+    }
+
+    /**
+     * Unconditionally throws {@link IndexOutOfBoundsException},
+     * since we can not get any character from an empty string.
+     */
+    @Override
+    public char charAt(final int index) {
+        throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1,
index));
+    }
+
+    /**
+     * Unconditionally returns en empty string.
+     */
+    @Override
+    public String toString() {
+        return "";
+    }
+
+    /**
+     * Unconditionally returns en empty string.
+     */
+    @Override
+    public String toString(final Locale locale) {
+        return "";
+    }
+
+    /**
+     * Returns {@code this} if the range is {@code [0…0]}, or throws an exception otherwise.
+     */
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        if (start == 0 && end == 0) {
+            return this;
+        }
+        throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IllegalRange_2, start,
end));
+    }
+
+    /**
+     * Returns 0 if the other string is empty, or -1 otherwise.
+     */
+    @Override
+    public int compareTo(final InternationalString other) {
+        return other.length() == 0 ? 0 : -1;
+    }
+
+    /*
+     * Do not override equals and hashCode. It is okay to keep the reference-equality semantic
+     * because all NilInternationalString instances are uniques in the running JVM.
+     */
+
+    /**
+     * Invoked on deserialization for replacing the deserialized instance by the unique instance.
+     */
+    private Object readResolve() throws ObjectStreamException {
+        return reason.createNilObject(InternationalString.class);
+    }
+}

Propchange: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilInternationalString.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilInternationalString.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java?rev=1508068&r1=1508067&r2=1508068&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java [UTF-8]
(original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilReason.java [UTF-8]
Mon Jul 29 13:49:07 2013
@@ -16,17 +16,20 @@
  */
 package org.apache.sis.xml;
 
+import java.util.Map;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.io.Serializable;
 import java.lang.reflect.Proxy;
 import java.lang.reflect.InvocationHandler;
+import org.opengis.util.InternationalString;
 import org.apache.sis.util.Immutable;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.collection.WeakHashSet;
+import org.apache.sis.util.collection.WeakValueHashMap;
 import org.apache.sis.internal.jaxb.PrimitiveTypeProperties;
 
 
@@ -144,26 +147,6 @@ public final class NilReason implements 
     private final Object reason;
 
     /**
-     * Special boolean value for attaching this {@code NilReason} to a {@code Boolean}.
-     */
-    private transient Boolean nilBoolean;
-
-    /**
-     * Special integer value for attaching this {@code NilReason} to an {@code Integer}.
-     */
-    private transient Integer nilInteger;
-
-    /**
-     * Special double value for attaching this {@code NilReason} to a {@code Double}.
-     */
-    private transient Double nilDouble;
-
-    /**
-     * Special string value for attaching this {@code NilReason} to a {@code String}.
-     */
-    private transient String nilString;
-
-    /**
      * The invocation handler for {@link NilObject} instances, created when first needed.
      * The same handler can be shared for all objects having the same {@code NilReason},
      * no matter the interface they implement.
@@ -171,6 +154,13 @@ public final class NilReason implements 
     private transient InvocationHandler handler;
 
     /**
+     * The values created by {@link #createNilObject(Class)}. They are often instances of
{@link NilObject},
+     * except for some JDK types like {@link String}, {@link Boolean} or {@link Integer}
which are handled
+     * in a special way.
+     */
+    private transient Map<Class<?>, Object> nilObjects;
+
+    /**
      * Creates a new reason for the given XML value or the given URI.
      */
     private NilReason(final Object reason) {
@@ -375,80 +365,93 @@ public final class NilReason implements 
      * @return An {@link NilObject} of the given type.
      */
     @SuppressWarnings("unchecked")
-    public <T> T createNilObject(final Class<T> type) {
+    public synchronized <T> T createNilObject(final Class<T> type) {
         ArgumentChecks.ensureNonNull("type", type);
-        if (!type.isInterface()) {
-            return createNilPrimitive(type);
-        }
-        if (NilObjectHandler.isIgnoredInterface(type)) {
-            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"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 'createNilPrimitive(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 an other 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);
         }
-        InvocationHandler h;
-        synchronized (this) {
-            if ((h = handler) == null) {
-                handler = h = new NilObjectHandler(this);
+        Object object = nilObjects.get(type);
+        if (object == null) {
+            /*
+             * If no object has been previously created, check for the usual case where the
requested type
+             * is an interface. We still have a special case for InternationalString. For
all other cases,
+             * we will rely on the proxy.
+             */
+            if (type.isInterface()) {
+                if (NilObjectHandler.isIgnoredInterface(type)) {
+                    throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"type", type));
+                }
+                if (type == InternationalString.class) {
+                    object = new NilInternationalString(this);
+                } else {
+                    if (handler == null) {
+                        handler = new NilObjectHandler(this);
+                    }
+                    object = Proxy.newProxyInstance(NilReason.class.getClassLoader(),
+                            new Class<?>[] {type, NilObject.class, LenientComparable.class},
handler);
+                }
+            } else {
+                /*
+                 * If the requested type is not an interface, this is usually an error except
for a short
+                 * list of special cases: Boolean, Integer, Double or String.
+                 */
+                object = createNilPrimitive(type);
+                PrimitiveTypeProperties.associate(object, this);
+            }
+            if (nilObjects.put(type, object) != null) {
+                throw new AssertionError(type); // Should never happen.
             }
         }
-        return (T) Proxy.newProxyInstance(NilReason.class.getClassLoader(),
-                new Class<?>[] {type, NilObject.class, LenientComparable.class}, h);
+        return type.cast(object);
     }
 
     /**
-     * Returns an {@code Boolean}, {@code Integer}, {@code Double} or {@code String} which
is nil for the reason
-     * represented by this instance.
+     * Returns an {@code Boolean}, {@code Integer}, {@code Double} or {@code String} to be
considered as a nil value.
+     * The caller is responsible for registering the value in {@link PrimitiveTypeProperties}.
+     *
+     * <p><b>REMINDER:<b> If more special cases are added, do not forget
to update the {@link #mayBeNil(Object)}
+     * method and to update javadoc.</p>
      *
      * @throws IllegalArgumentException If the given type is not a supported type.
      */
-    @SuppressWarnings("unchecked")
-    private <T> T createNilPrimitive(final Class<T> type) {
-        if (type == Boolean.class) {
-            Boolean value;
-            synchronized (this) {
-                if ((value = nilBoolean) == null) {
-                    nilBoolean = value = new Boolean(false); // REALLY need a new instance,
not Boolean.FALSE.
-                    PrimitiveTypeProperties.associate(value, this);
-                }
-            }
-            return (T) value;
-        }
-        if (type == Integer.class) {
-            Integer value;
-            synchronized (this) {
-                if ((value = nilInteger) == null) {
-                    nilInteger = value = new Integer(0); // REALLY need a new instance, not
Integer.valueOf(…).
-                    PrimitiveTypeProperties.associate(value, this);
-                }
-            }
-            return (T) value;
-        }
-        if (type == Double.class) {
-            Double value;
-            synchronized (this) {
-                if ((value = nilDouble) == null) {
-                    nilDouble = value = new Double(Double.NaN); // REALLY need a new instance,
not Double.valueOf(…).
-                    PrimitiveTypeProperties.associate(value, this);
-                }
-            }
-            return (T) value;
-        }
-        if (type == String.class) {
-            String value;
-            synchronized (this) {
-                if ((value = nilString) == null) {
-                    nilString = value = new String(""); // REALLY need a new instance.
-                    PrimitiveTypeProperties.associate(value, this);
-                }
-            }
-            return (T) value;
-        }
-        /*
-         * REMINDER: If more special cases are added, do not forget to update the getNilReason(Object)
method
-         *           below and to update javadoc.
-         */
+    private static Object createNilPrimitive(final Class<?> type) {
+        if (type == String .class) return new String("");         // REALLY need a new instance.
+        if (type == Integer.class) return new Integer(0);         // REALLY need a new instance,
not Integer.valueOf(0).
+        if (type == Boolean.class) return new Boolean(false);     // REALLY need a new instance,
not Boolean.FALSE.
+        if (type == Double .class) return new Double(Double.NaN); // REALLY need a new instance,
not Double.valueOf(…).
         throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"type", type));
     }
 
     /**
+     * Returns {@code true} if the given object may be one of the sentinel values
+     * created by {@link #createNilPrimitive(Class)}. This method only checks the value.
+     * If this method returns {@code true}, then the caller still need to check the actual
instance using the
+     * {@link PrimitiveTypeProperties} class. The purpose of this method is to filter the
values that can not
+     * be sentinel values, in order to avoid the synchronization done by {@code PrimitiveTypeProperties}.
+     */
+    private static boolean mayBeNil(final Object object) {
+        // 'instanceof' checks on instances of final classes are expected to be very fast.
+        if (object instanceof String)  return ((String)  object).isEmpty();
+        if (object instanceof Integer) return ((Integer) object).intValue() == 0;
+        if (object instanceof Boolean) return ((Boolean) object).booleanValue() == false;
+        if (object instanceof Double)  return Double.isNaN(((Double) object).doubleValue());
+        return false;
+    }
+
+    /**
      * If the given object is nil, returns the reason why it does not contain information.
      * This method performs the following choices:
      *
@@ -474,17 +477,7 @@ public final class NilReason implements 
             if (object instanceof NilObject) {
                 return ((NilObject) object).getNilReason();
             }
-            /*
-             * Invoke 'PrimitiveTypeProperties' method only if the given type is one of the
hard-coded
-             * types from the above 'createNilPrimitive(Object)' method, in order to avoid
unnecessary
-             * synchronization (implicitly done by PrimitiveTypeProperties). Note that 'instanceof'
-             * checks on instances of final classes are expected to be very fast.
-             */
-            if ((object instanceof Boolean && ((Boolean) object).booleanValue() ==
false) ||
-                (object instanceof Integer && ((Integer) object).intValue() == 0)
||
-                (object instanceof Double  && Double.isNaN(((Double) object).doubleValue()))
||
-                (object instanceof String  && ((String) object).isEmpty()))
-            {
+            if (mayBeNil(object)) {
                 return (NilReason) PrimitiveTypeProperties.property(object);
             }
         }

Modified: sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java?rev=1508068&r1=1508067&r2=1508068&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java
[UTF-8] Mon Jul 29 13:49:07 2013
@@ -17,6 +17,7 @@
 package org.apache.sis.xml;
 
 import java.net.URISyntaxException;
+import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.util.LenientComparable;
@@ -33,7 +34,7 @@ import static org.apache.sis.test.Assert
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.18)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public final strictfp class NilReasonTest extends TestCase {
@@ -94,6 +95,91 @@ public final strictfp class NilReasonTes
     }
 
     /**
+     * Tests {@link NilReason#createNilObject(Class)} for a boolean type.
+     * Opportunistically tests {@link NilReason#forObject(Object)} with the created object.
+     *
+     * @since 0.4
+     */
+    @Test
+    public void testCreateNilBoolean() {
+        final Boolean value = NilReason.MISSING.createNilObject(Boolean.class);
+        assertEquals (Boolean.FALSE, value);
+        assertNotSame(Boolean.FALSE, value);
+        assertSame("NilReason.forObject(…)", NilReason.MISSING, NilReason.forObject(value));
+        assertNull("NilReason.forObject(…)", NilReason.forObject(Boolean.FALSE));
+        assertNull("NilReason.forObject(…)", NilReason.forObject(Boolean.TRUE));
+        assertSame("Expected cached value.", value, NilReason.MISSING.createNilObject(Boolean.class));
+    }
+
+    /**
+     * Tests {@link NilReason#createNilObject(Class)} for an integer type.
+     * Opportunistically tests {@link NilReason#forObject(Object)} with the created object.
+     *
+     * @since 0.4
+     */
+    @Test
+    public void testCreateNilInteger() {
+        final Integer zero  = 0;
+        final Integer value = NilReason.MISSING.createNilObject(Integer.class);
+        assertEquals (zero, value);
+        assertNotSame(zero, value);
+        assertSame("NilReason.forObject(…)", NilReason.MISSING, NilReason.forObject(value));
+        assertNull("NilReason.forObject(…)", NilReason.forObject(zero));
+        assertNull("NilReason.forObject(…)", NilReason.forObject(1));
+        assertSame("Expected cached value.", value, NilReason.MISSING.createNilObject(Integer.class));
+    }
+
+    /**
+     * Tests {@link NilReason#createNilObject(Class)} for a double type.
+     * Opportunistically tests {@link NilReason#forObject(Object)} with the created object.
+     *
+     * @since 0.4
+     */
+    @Test
+    public void testCreateNilDouble() {
+        final Double nan  = Double.NaN;
+        final Double value = NilReason.MISSING.createNilObject(Double.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(0.0));
+        assertSame("Expected cached value.", value, NilReason.MISSING.createNilObject(Double.class));
+    }
+
+    /**
+     * Tests {@link NilReason#createNilObject(Class)} for a string type.
+     * Opportunistically tests {@link NilReason#forObject(Object)} with the created object.
+     *
+     * @since 0.4
+     */
+    @Test
+    public void testCreateNilString() {
+        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));
+    }
+
+    /**
+     * Tests {@link NilReason#createNilObject(Class)} for an international string type.
+     * Opportunistically tests {@link NilReason#forObject(Object)} with the created object.
+     *
+     * @since 0.4
+     */
+    @Test
+    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));
+    }
+
+    /**
      * Tests the creation of {@link NilObject} instances.
      */
     @Test
@@ -103,6 +189,8 @@ public final strictfp class NilReasonTes
         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));
     }
 
     /**



Mime
View raw message