sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1416555 - in /sis/branches/JDK7/sis-utility/src: main/java/org/apache/sis/internal/jaxb/ main/java/org/apache/sis/util/resources/ main/java/org/apache/sis/xml/ test/java/org/apache/sis/internal/jaxb/
Date Mon, 03 Dec 2012 15:11:51 GMT
Author: desruisseaux
Date: Mon Dec  3 15:11:48 2012
New Revision: 1416555

URL: http://svn.apache.org/viewvc?rev=1416555&view=rev
Log:
Keep trace of objects associated to UUID in the current JVM.
This is a first draft - will need more review and tests.

Added:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierAlreadyBoundException.java
  (with props)
Modified:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/UUIDs.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java
(original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java
Mon Dec  3 15:11:48 2012
@@ -28,14 +28,15 @@ import java.io.Serializable;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.xml.IdentifierSpace;
 import org.apache.sis.xml.IdentifierMap;
+import org.apache.sis.xml.IdentifierSpace;
+import org.apache.sis.xml.IdentifierAlreadyBoundException;
+
+import static org.apache.sis.util.collection.Collections.hashMapCapacity;
 
 // Related to JDK7
 import java.util.Objects;
 
-import static org.apache.sis.util.collection.Collections.hashMapCapacity;
-
 
 /**
  * A map of identifiers which can be used as a helper class for
@@ -106,16 +107,6 @@ public class IdentifierMapAdapter extend
     private transient Set<Entry<Citation,String>> entries;
 
     /**
-     * Creates an identifier map for the given collection of identifiers.
-     *
-     * @param  identifiers The identifiers to wrap in a map view.
-     * @return The map of identifiers as a wrapper over the given collection.
-     */
-    public static IdentifierMap create(final Collection<Identifier> identifiers) {
-        return new IdentifierMapWithSpecialCases(identifiers);
-    }
-
-    /**
      * Creates a new map which will be a view over the given identifiers.
      *
      * @param identifiers The identifiers to wrap in a map view.
@@ -200,6 +191,25 @@ public class IdentifierMapAdapter extend
     }
 
     /**
+     * Returns the code of the first identifier associated with the given authority only
if
+     * if is <strong>not</strong> a specialized identifier. Otherwise returns
{@code null}.
+     *
+     * <p>This is a helper method for {@link IdentifierMapWithSpecialCases#put(Citation,
String)},
+     * in order to be able to return the old value if that value was a {@link String} rather
than
+     * the specialized type. We do not return the string for the specialized case in order
to avoid
+     * the cost of invoking {@code toString()} on the specialized object (some may be costly).
Such
+     * call would be useless because {@code IdentifierMapWithSpecialCase} discard the value
of this
+     * method when it found a specialized type.</p>
+     */
+    final String getUnspecialized(final Citation authority) {
+        final Identifier identifier = getIdentifier(authority);
+        if (identifier != null && !(identifier instanceof SpecializedIdentifier<?>))
{
+            return identifier.getCode();
+        }
+        return null;
+    }
+
+    /**
      * Returns the code of the first identifier associated with the given
      * {@linkplain Identifier#getAuthority() authority}, or {@code null}
      * if no identifier was found.
@@ -219,8 +229,8 @@ public class IdentifierMapAdapter extend
     }
 
     /**
-     * Removes all identifiers associated with the given
-     * {@linkplain Identifier#getAuthority() authority}.
+     * Removes all identifiers associated with the given {@linkplain Identifier#getAuthority()
authority}.
+     * The default implementation delegates to {@link #put(Citation, String)} with a {@code
null} value.
      *
      * @param  authority The authority to search, which should be an instance of {@link Citation}.
      * @return The code of the identifier for the given authority, or {@code null} if none.
@@ -238,11 +248,15 @@ public class IdentifierMapAdapter extend
      * first entry, so it can be find by the {@code get} method.
      *
      * @param  authority The authority for which to set the code.
-     * @param  code The new code for the given authority.
+     * @param  code The new code for the given authority, or {@code null} for removing the
entry.
      * @return The previous code for the given authority, or {@code null} if none.
+     * @throws IdentifierAlreadyBoundException If this map expects unique identifiers for
the
+     *         given authority, and the given value is already associated to another object.
      */
     @Override
-    public String put(final Citation authority, final String code) throws UnsupportedOperationException
{
+    public String put(final Citation authority, final String code)
+            throws IdentifierAlreadyBoundException, UnsupportedOperationException
+    {
         ArgumentChecks.ensureNonNull("authority", authority);
         String old = null;
         final Iterator<? extends Identifier> it = identifiers.iterator();
@@ -275,7 +289,9 @@ public class IdentifierMapAdapter extend
      * Sets the identifier associated with the given authority, and returns the previous
value.
      */
     @Override
-    public <T> T putSpecialized(final IdentifierSpace<T> authority, final T value)
throws UnsupportedOperationException {
+    public <T> T putSpecialized(final IdentifierSpace<T> authority, final T value)
+            throws IdentifierAlreadyBoundException, UnsupportedOperationException
+    {
         ArgumentChecks.ensureNonNull("authority", authority);
         T old = null;
         final Iterator<? extends Identifier> it = identifiers.iterator();

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java
(original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java
Mon Dec  3 15:11:48 2012
@@ -18,10 +18,12 @@ package org.apache.sis.internal.jaxb;
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.UUID;
 import java.util.Collection;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.xml.IdentifierSpace;
+import org.apache.sis.xml.IdentifierAlreadyBoundException;
 import org.apache.sis.xml.XLink;
 
 // Related to JDK7
@@ -30,40 +32,53 @@ import java.util.Objects;
 
 /**
  * A map of identifiers which handles some identifiers in a special way.
- * The identifiers for the following authorities are handled in a special way.
- * See usages of {@link #specialCase(Object)} for spotting the code where
- * a special handling is applied.
+ * The identifiers for the following authorities are handled in a special way:
  *
  * <ul>
- *   <li>{@link IdentifierSpace#HREF}, handled as a shortcut to {@link XLink#getHRef()}.</li>
+ *   <li>{@link IdentifierSpace#HREF}: handled as a shortcut to {@link XLink#getHRef()}.</li>
+ *   <li>{@link IdentifierSpace#UUID}: {@code put} operations register the UUID in
a shared map.</li>
  * </ul>
  *
+ * See usages of {@link #specialCase(Object)} for identifying the code locations where a
special
+ * handling is applied.
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.19)
  * @version 0.3
  * @module
  */
-final class IdentifierMapWithSpecialCases extends IdentifierMapAdapter {
+public final class IdentifierMapWithSpecialCases extends IdentifierMapAdapter {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = 5139573827448780289L;
 
     /**
+     * The object being referenced by the identifiers, or {@code null} if not applicable.
+     */
+    private final Object referent;
+
+    /**
      * Creates a new map which will be a view over the given identifiers.
      *
      * @param identifiers The identifiers to wrap in a map view.
+     * @param referent    The object being referenced by the identifiers.
      */
-    public IdentifierMapWithSpecialCases(final Collection<Identifier> identifiers)
{
+    public IdentifierMapWithSpecialCases(final Collection<Identifier> identifiers,
final Object referent) {
         super(identifiers);
+        this.referent = referent;
     }
 
     /**
      * If the given authority is a special case, returns its {@link NonMarshalledAuthority}
      * integer enum. Otherwise returns -1. See javadoc for more information about special
cases.
+     *
+     * @param authority A {@link Citation} constant. The type is relaxed to {@code Object}
+     *        because the signature of some {@code Map} methods are that way.
      */
     private static int specialCase(final Object authority) {
         if (authority == IdentifierSpace.HREF) return NonMarshalledAuthority.HREF;
+        if (authority == IdentifierSpace.UUID) return NonMarshalledAuthority.UUID;
         // A future Apache SIS version may add more special cases here.
         return -1;
     }
@@ -91,19 +106,42 @@ final class IdentifierMapWithSpecialCase
      * object.
      */
     private URI setHRef(final URI href) {
-        super.putSpecialized(IdentifierSpace.HREF, null);
+        URI old = super.putSpecialized(IdentifierSpace.HREF, null);
         XLink link = super.getSpecialized(IdentifierSpace.XLINK);
         if (link != null) {
-            final URI old = link.getHRef();
+            if (old == null) {
+                old = link.getHRef();
+            }
             link.setHRef(href);
-            return old;
-        }
-        if (href != null) {
+        } else if (href != null) {
             link = new XLink();
             link.setHRef(href);
             super.putSpecialized(IdentifierSpace.XLINK, link);
         }
-        return null;
+        return old;
+    }
+
+    /**
+     * Sets the {@code "gco:uuid"} value, which may be null. This method stores the UUID-object
+     * association in a shared map, if no value existed previously.
+     *
+     * @param  uuid The UUID to assign to the object.
+     * @return The previous value, or {@code null} if none.
+     * @throws IdentifierAlreadyBoundException If the given identifier is already associated
to another object.
+     */
+    private UUID setUUID(final UUID uuid) throws IdentifierAlreadyBoundException {
+        if (referent == null) {
+            return super.putSpecialized(IdentifierSpace.UUID, uuid);
+        }
+        if (uuid != null) {
+            UUIDs.bind(uuid, referent); // May throws IdentifierAlreadyBoundException
+        }
+        // Invoke 'put' only if UUIDs.bind(…) has been succesful.
+        final UUID old = super.putSpecialized(IdentifierSpace.UUID, uuid);
+        if (old != null && !old.equals(uuid)) {
+            UUIDs.unbind(old, referent);
+        }
+        return old;
     }
 
     /**
@@ -173,19 +211,40 @@ final class IdentifierMapWithSpecialCase
      * {@inheritDoc}
      */
     @Override
-    public String put(final Citation authority, final String code) throws UnsupportedOperationException
{
+    public String put(final Citation authority, final String code)
+            throws IdentifierAlreadyBoundException, UnsupportedOperationException
+    {
+        final Exception exception;
         switch (specialCase(authority)) {
+            default: {
+                return super.put(authority, code);
+            }
             case NonMarshalledAuthority.HREF: {
-                try {
-                    final URI old = setHRef((code != null) ? new URI(code) : null);
-                    return (old != null) ? old.toString() : null;
+                URI id = null;
+                if (code != null) try {
+                    id = new URI(code);
                 } catch (URISyntaxException e) {
-                    // Do not log the exception, since it will be
-                    // reported by super.put(Citation, String).
+                    exception = e;
+                    break;
                 }
-                break;
+                final String old = getUnspecialized(authority);
+                id = setHRef(id);
+                return (id != null) ? id.toString() : old;
+            }
+            case NonMarshalledAuthority.UUID: {
+                UUID id = null;
+                if (code != null) try {
+                    id = UUID.fromString(code);
+                } catch (IllegalArgumentException e) {
+                    exception = e;
+                    break;
+                }
+                final String old = getUnspecialized(authority);
+                id = setUUID(id);
+                return (id != null) ? id.toString() : old;
             }
         }
+        SpecializedIdentifier.parseFailure(exception);
         return super.put(authority, code);
     }
 
@@ -194,12 +253,13 @@ final class IdentifierMapWithSpecialCase
      */
     @Override
     @SuppressWarnings("unchecked")
-    public <T> T putSpecialized(final IdentifierSpace<T> authority, final T value)
throws UnsupportedOperationException {
+    public <T> T putSpecialized(final IdentifierSpace<T> authority, final T value)
+            throws IdentifierAlreadyBoundException, UnsupportedOperationException
+    {
         switch (specialCase(authority)) {
-            case NonMarshalledAuthority.HREF: {
-                return (T) setHRef((URI) value);
-            }
+            default: return super.putSpecialized(authority, value);
+            case NonMarshalledAuthority.HREF: return (T) setHRef((URI)  value);
+            case NonMarshalledAuthority.UUID: return (T) setUUID((UUID) value);
         }
-        return super.putSpecialized(authority, value);
     }
 }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
(original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
Mon Dec  3 15:11:48 2012
@@ -139,7 +139,7 @@ public final class SpecializedIdentifier
      * This is considered a non-fatal error, because the parse method can fallback
      * on the generic {@link IdentifierMapEntry} in such cases.
      */
-    private static void parseFailure(final Exception e) {
+    static void parseFailure(final Exception e) {
         // IdentifierMap.put(Citation,String) is the public facade.
         Logging.recoverableException(IdentifierMap.class, "put", e);
     }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/UUIDs.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/UUIDs.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/UUIDs.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/UUIDs.java Mon
Dec  3 15:11:48 2012
@@ -19,6 +19,7 @@ package org.apache.sis.internal.jaxb;
 import java.util.UUID;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.collection.WeakValueHashMap;
+import org.apache.sis.xml.IdentifierAlreadyBoundException;
 
 
 /**
@@ -60,14 +61,33 @@ public final class UUIDs extends Static 
     /**
      * Keep a weak references to the given object for the given UUID.
      * If an object is already mapped to the given UUID, then the mapping is <strong>not</strong>
-     * modified and the currently mapped object is returned. The returned object may or may
not be
-     * equals to the object given in argument to this method.
+     * modified. An exception is thrown instead.
      *
      * @param  uuid   The UUID to associate to the object.
      * @param  object The object to associate to the UUID.
-     * @return If an object is already mapped to the given UUID, that object. Otherwise {@code
null}.
+     * @throws IdentifierAlreadyBoundException If the given identifier is already associated
to another object.
      */
-    public static Object store(final UUID uuid, final Object object) {
-        return OBJECTS.putIfAbsent(uuid, object);
+    static void bind(final UUID uuid, final Object object) throws IdentifierAlreadyBoundException
{
+        final Object old = OBJECTS.putIfAbsent(uuid, object);
+        if (old != null && old != object) {
+            throw new IdentifierAlreadyBoundException(null, uuid);
+        }
+    }
+
+    /**
+     * Removes the entry associated to the given UUID.
+     * If the given UUID is associated to another object than the given one,
+     * then this method does nothing.
+     *
+     * @param uuid   The UUID of the entry to remove from the map.
+     * @param object The object to remove from the map.
+     */
+    static void unbind(final UUID uuid, final Object object) {
+        synchronized (OBJECTS) {
+            final Object old = OBJECTS.remove(uuid);
+            if (old != null && old != object) { // Same work than ConcurrentMap.remove(Object,
Object);
+                OBJECTS.put(uuid, old);
+            }
+        }
     }
 }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
(original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
Mon Dec  3 15:11:48 2012
@@ -89,6 +89,11 @@ public final class Errors extends Indexe
         public static final int ForbiddenAttribute_2 = 21;
 
         /**
+         * Identifier “{0}” is already associated to another object.
+         */
+        public static final int IdentifierAlreadyBound_1 = 50;
+
+        /**
          * Argument ‘{0}’ can not be an instance of ‘{1}’.
          */
         public static final int IllegalArgumentClass_2 = 17;

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
(original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
Mon Dec  3 15:11:48 2012
@@ -32,6 +32,7 @@ IllegalLanguageCode_1           = The \u
 IllegalRange_2                  = Range [{0} \u2026 {1}] is not valid.
 InconsistentAttribute_2         = Value \u201c{1}\u201d of attribute \u2018{0}\u2019 is inconsistent
with other attributes.
 InconsistentTableColumns        = Inconsistent table columns.
+IdentifierAlreadyBound_1        = Identifier \u201c{0}\u201d is already associated to another
object.
 IndexOutOfBounds_1              = Index {0} is out of bounds.
 InfiniteArgumentValue_1         = Argument \u2018{0}\u2019 can not take an infinite value.
 KeyCollision_1                  = A different value is already associated to the \u201c{0}\u201d
key.

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
(original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
Mon Dec  3 15:11:48 2012
@@ -32,6 +32,7 @@ IllegalLanguageCode_1           = Le cod
 IllegalRange_2                  = La plage [{0} \u2026 {1}] n\u2019est pas valide.
 InconsistentAttribute_2         = La valeur \u201c{1}\u201d de l\u2019attribut \u2018{0}\u2019
n\u2019est pas coh\u00e9rente avec celles des autres attributs.
 InconsistentTableColumns        = Les colonnes des tables ne sont pas coh\u00e9rentes.
+IdentifierAlreadyBound_1        = L\u2019identifiant \u201c{0}\u201d est d\u00e9j\u00e0 associ\u00e9
\u00e0 un autre objet.
 IndexOutOfBounds_1              = L\u2019index {0} est en dehors des limites permises.
 InfiniteArgumentValue_1         = L\u2019argument \u2018{0}\u2019 ne peut pas prendre une
valeur infinie.
 KeyCollision_1                  = Une valeur diff\u00e9rente est d\u00e9j\u00e0 associ\u00e9e
\u00e0 la cl\u00e9 \u201c{0}\u201d.

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierAlreadyBoundException.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierAlreadyBoundException.java?rev=1416555&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierAlreadyBoundException.java
(added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierAlreadyBoundException.java
Mon Dec  3 15:11:48 2012
@@ -0,0 +1,90 @@
+/*
+ * 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.UUID;
+import java.util.Locale;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.util.LocalizedException;
+
+
+/**
+ * Throws when an object is given a {@code UUID} which is already assigned to another object.
+ * While the same XML identifier can be used in different documents, {@link UUID}s are expected
+ * to be truly unique.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public class IdentifierAlreadyBoundException extends IllegalArgumentException implements
LocalizedException {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -7571733911210491933L;
+
+    /**
+     * The locale to use for formatting the {@linkplain #getLocalizedMessage() localized
error message}.
+     */
+    private final Locale locale;
+
+    /**
+     * The identifier which is already associated to another object.
+     * May be an instance of {@link UUID} or {@link XLink}.
+     *
+     * (Current constructor accepts only UUID, but we may
+     * expand the set of allowed types in a future version).
+     */
+    private final Object identifier;
+
+    /**
+     * Creates a new exception for the given identifier.
+     *
+     * @param locale     The locale to use for the {@linkplain #getLocalizedMessage() localized
message}.
+     * @param identifier The identifier which is already associated to another object.
+     */
+    public IdentifierAlreadyBoundException(final Locale locale, final UUID identifier) {
+        super(Errors.format(Errors.Keys.IdentifierAlreadyBound_1, identifier));
+        this.locale     = locale;
+        this.identifier = identifier;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getLocalizedMessage() {
+        return Errors.getResources(locale).getString(Errors.Keys.IdentifierAlreadyBound_1,
identifier);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getLocalizedMessage(final Locale locale) {
+        return Errors.getResources(locale).getString(Errors.Keys.IdentifierAlreadyBound_1,
identifier);
+    }
+}

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

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierAlreadyBoundException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java Mon
Dec  3 15:11:48 2012
@@ -64,7 +64,10 @@ public interface IdentifierMap extends M
      * @param  value The identifier to be associated with the given namespace.
      * @return The previous identifier associated with {@code authority}, or {@code null}
      *         if there was no mapping of the specialized type for {@code authority}.
+     * @throws IdentifierAlreadyBoundException If this map expects unique identifiers for
the
+     *         given authority, and the given value is already associated to another object.
      * @throws UnsupportedOperationException If the identifier map is unmodifiable.
      */
-    <T> T putSpecialized(IdentifierSpace<T> authority, T value) throws UnsupportedOperationException;
+    <T> T putSpecialized(IdentifierSpace<T> authority, T value)
+            throws IdentifierAlreadyBoundException, UnsupportedOperationException;
 }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java Mon
Dec  3 15:11:48 2012
@@ -31,6 +31,7 @@ import org.apache.sis.util.ComparisonMod
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.jaxb.IdentifierMapAdapter;
+import org.apache.sis.internal.jaxb.IdentifierMapWithSpecialCases;
 
 // Related to JDK7
 import java.util.Objects;
@@ -71,7 +72,7 @@ final class NilObjectHandler implements 
                 asList.add(identifier);
             }
         }
-        attribute = IdentifierMapAdapter.create(asList);
+        attribute = new IdentifierMapWithSpecialCases(asList, null);
     }
 
     /**

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java
(original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java
Mon Dec  3 15:11:48 2012
@@ -82,59 +82,71 @@ public strictfp class IdentifierMapAdapt
     public void testGetAndPut() {
         final List<Identifier> identifiers = new ArrayList<>();
         final Map<Citation,String> map = create(identifiers);
-        assertTrue(map.isEmpty());
-        assertEquals(0, map.size());
-
+        assertTrue  ("Newly created map shall be empty.", map.isEmpty());
+        assertEquals("Newly created map shall be empty.", 0, map.size());
+        /*
+         * Add two entries, then verify the map content.
+         */
         identifiers.add(new IdentifierMapEntry(ID,   "myID"));
         identifiers.add(new IdentifierMapEntry(UUID, "myUUID"));
-        assertFalse (map.isEmpty());
-        assertEquals(2, map.size());
-        assertEquals(2, identifiers.size());
-        assertTrue  (map.containsKey(ID));
-        assertTrue  (map.containsKey(UUID));
-        assertFalse (map.containsKey(HREF));
-        assertEquals("myID",   map.get(ID));
-        assertEquals("myUUID", map.get(UUID));
-        assertNull  (          map.get(HREF));
-        assertTrue  (map.containsValue("myID"));
-        assertTrue  (map.containsValue("myUUID"));
-        assertFalse (map.containsValue("myHREF"));
+        assertFalse ("After add, map shall not be empty.", map.isEmpty());
+        assertEquals("After add, map shall not be empty.", 2, map.size());
+        assertEquals("After add, map shall not be empty.", 2, identifiers.size());
+        assertTrue  ("Shall contain the entry we added.",        map.containsKey(ID));
+        assertTrue  ("Shall contain the entry we added.",        map.containsKey(UUID));
+        assertFalse ("Shall not contain entry we didn't added.", map.containsKey(HREF));
+        assertTrue  ("Shall contain the entry we added.",        map.containsValue("myID"));
+        assertTrue  ("Shall contain the entry we added.",        map.containsValue("myUUID"));
+        assertFalse ("Shall not contain entry we didn't added.", map.containsValue("myHREF"));
+        assertEquals("Shall contain the entry we added.",        "myID",   map.get(ID));
+        assertEquals("Shall contain the entry we added.",        "myUUID", map.get(UUID));
+        assertNull  ("Shall not contain entry we didn't added.",           map.get(HREF));
         assertMapEquals("{gml:id=“myID”, gco:uuid=“myUUID”}", map);
-
-        assertEquals("myUUID", map.put(UUID, "myNewUUID"));
-        assertFalse (map.containsValue("myUUID"));
-        assertTrue  (map.containsValue("myNewUUID"));
+        /*
+         * Alter one entry (no new entry added).
+         */
+        assertEquals("Shall get the old value.",       "myUUID", map.put(UUID, "myNewUUID"));
+        assertFalse ("Shall not contain anymore the old value.", map.containsValue("myUUID"));
+        assertTrue  ("Shall contain the new value.",             map.containsValue("myNewUUID"));
         assertMapEquals("{gml:id=“myID”, gco:uuid=“myNewUUID”}", map);
-        assertEquals(2, map.size());
-        assertEquals(2, identifiers.size());
-
-        assertNull  (map.put(HREF, "myHREF"));
-        assertTrue  (map.containsValue("myHREF"));
-        assertTrue  (map.containsKey(HREF));
+        assertEquals("Map size shall be unchanged.", 2, map.size());
+        assertEquals("Map size shall be unchanged.", 2, identifiers.size());
+        /*
+         * Add a third identifier.
+         */
+        assertNull  ("Shall not contain entry we didn't added.", map.put(HREF, "myHREF"));
+        assertTrue  ("Shall contain the entry we added.",        map.containsValue("myHREF"));
+        assertTrue  ("Shall contain the entry we added.",        map.containsKey(HREF));
         assertMapEquals("{gml:id=“myID”, gco:uuid=“myNewUUID”, xlink:href=“myHREF”}",
map);
-        assertEquals(3, map.size());
-        assertEquals(3, identifiers.size());
-
-        assertEquals("myNewUUID", map.remove(UUID));
-        assertFalse (map.containsValue("myNewUUID"));
-        assertFalse (map.containsKey(UUID));
+        assertEquals("Map size shall be updated.", 3, map.size());
+        assertEquals("Map size shall be updated.", 3, identifiers.size());
+        /*
+         * Remove an identifier using the Map.remove(…) API.
+         */
+        assertEquals("Shall get the old value.",   "myNewUUID", map.remove(UUID));
+        assertFalse ("Shall not contain the entry we removed.", map.containsValue("myNewUUID"));
+        assertFalse ("Shall not contain the entry we removed.", map.containsKey(UUID));
         assertMapEquals("{gml:id=“myID”, xlink:href=“myHREF”}", map);
-        assertEquals(2, map.size());
-        assertEquals(2, identifiers.size());
-
+        assertEquals("Map size shall be updated.", 2, map.size());
+        assertEquals("Map size shall be updated.", 2, identifiers.size());
+        /*
+         * Remove an identifier using the Set.remove(…) API on values.
+         */
         assertTrue  (map.values().remove(toHRefString("myHREF")));
-        assertFalse (map.containsValue("myHREF"));
-        assertFalse (map.containsKey(HREF));
+        assertFalse ("Shall not contain the entry we removed.", map.containsValue("myHREF"));
+        assertFalse ("Shall not contain the entry we removed.", map.containsKey(HREF));
         assertMapEquals("{gml:id=“myID”}", map);
-        assertEquals(1, map.size());
-        assertEquals(1, identifiers.size());
-
+        assertEquals("Map size shall be updated.", 1, map.size());
+        assertEquals("Map size shall be updated.", 1, identifiers.size());
+        /*
+         * Remove an identifier using the Set.remove(…) API on keys.
+         */
         assertTrue  (map.keySet().remove(ID));
-        assertFalse (map.containsValue("myID"));
-        assertFalse (map.containsKey(ID));
+        assertFalse ("Shall not contain the entry we removed.", map.containsValue("myID"));
+        assertFalse ("Shall not contain the entry we removed.", map.containsKey(ID));
         assertMapEquals("{}", map);
-        assertEquals(0, map.size());
-        assertEquals(0, identifiers.size());
+        assertEquals("Map size shall be updated.", 0, map.size());
+        assertEquals("Map size shall be updated.", 0, identifiers.size());
     }
 
     /**

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java?rev=1416555&r1=1416554&r2=1416555&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java
(original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java
Mon Dec  3 15:11:48 2012
@@ -56,7 +56,7 @@ public final strictfp class IdentifierMa
      */
     @Override
     IdentifierMapAdapter create(final Collection<Identifier> identifiers) {
-        return new IdentifierMapWithSpecialCases(identifiers);
+        return new IdentifierMapWithSpecialCases(identifiers, null);
     }
 
     /**



Mime
View raw message