sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1398723 - in /sis/branches/JDK7/sis-utility/src: main/java/org/apache/sis/internal/jaxb/ test/java/org/apache/sis/internal/jaxb/ test/java/org/apache/sis/test/ test/java/org/apache/sis/test/suite/
Date Tue, 16 Oct 2012 10:31:45 GMT
Author: desruisseaux
Date: Tue Oct 16 10:31:44 2012
New Revision: 1398723

URL: http://svn.apache.org/viewvc?rev=1398723&view=rev
Log:
Added IdentifierMap implementation.

Added:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java   (with props)
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java   (with props)
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java   (with props)
Modified:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java

Added: 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=1398723&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java Tue Oct 16 10:31:44 2012
@@ -0,0 +1,527 @@
+/*
+ * 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.internal.jaxb;
+
+import java.util.Set;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.AbstractSet;
+import java.util.AbstractMap;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+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 static org.apache.sis.util.collection.Collections.hashMapCapacity;
+
+
+/**
+ * A map of identifiers which can be used as a helper class for
+ * {@link org.geotoolkit.xml.IdentifiedObject} implementations.
+ *
+ * <p>This class works as a wrapper around a collection of identifiers. Because all operations
+ * are performed by an iteration over the collection elements, this implementation is suitable
+ * only for small maps (less than 10 elements). Given that objects typically have only one or
+ * two identifiers, this is considered acceptable.</p>
+ *
+ * {@section Handling of duplicated authorities}
+ * The collection shall not contains more than one identifier for the same
+ * {@linkplain Identifier#getAuthority() authority}. Nevertheless if such duplication
+ * is found, then this map implementation applies the following rules:
+ * <p>
+ * <ul>
+ *   <li>All getter methods (including the iterators and the values returned by the {@code put}
+ *       and {@code remove} methods) return only the identifier code associated to the first
+ *       occurrence of each authority. Any subsequent occurrences of the same authorities are
+ *       silently ignored.</li>
+ *   <li>All setter methods <em>may</em> affect <em>all</em> identifiers previously associated to
+ *       the given authority, not just the first occurrence. We do that in order to ensure that
+ *       the effect of setter methods are visible to subsequent calls to getter methods. Whatever
+ *       all occurrences or only the first one will be affected is implementation dependent.</li>
+ * </ul>
+ *
+ * {@section Handling of null identifiers}
+ * The collection of identifiers shall not contains any null element. However, in order to
+ * make the code more robust, any null element are skipped. Note however that it may cause some
+ * inconsistency, for example {@link #isEmpty()} could returns {@code false} while the more
+ * accurate {@link #size()} method returns 0.
+ *
+ * {@section Thread safety}
+ * This class is thread safe if the underlying identifier collection is thread safe.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.18)
+ * @version 0.3
+ * @module
+ *
+ * @see org.geotoolkit.xml.IdentifiedObject
+ */
+public class IdentifierMapAdapter extends AbstractMap<Citation,String> implements IdentifierMap, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 2661044384787218964L;
+
+    /**
+     * An immutable empty instance.
+     */
+    public static final IdentifierMap EMPTY = new IdentifierMapAdapter(Collections.<Identifier>emptySet());
+
+    /**
+     * The identifiers to wrap in a map view.
+     */
+    public final Collection<Identifier> identifiers;
+
+    /**
+     * A view over the entries, created only when first needed.
+     */
+    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.
+     */
+    IdentifierMapAdapter(final Collection<Identifier> identifiers) {
+        this.identifiers = identifiers;
+    }
+
+    /**
+     * Removes every entries in the underlying collection.
+     *
+     * @throws UnsupportedOperationException If the collection of identifiers is unmodifiable.
+     */
+    @Override
+    public void clear() throws UnsupportedOperationException {
+        identifiers.clear();
+    }
+
+    /**
+     * Returns {@code true} if the collection of identifiers contains at least one element.
+     * This method does not verify if the collection contains null element (it should not).
+     * Consequently, this method may return {@code false} even if the {@link #size()} method
+     * returns 0.
+     */
+    @Override
+    public boolean isEmpty() {
+        return identifiers.isEmpty();
+    }
+
+    /**
+     * Returns {@code true} if at least one identifier declares the given
+     * {@linkplain Identifier#getCode() code}.
+     *
+     * @param  code The code to search, which should be an instance of {@link String}.
+     * @return {@code true} if at least one identifier uses the given code.
+     */
+    @Override
+    public boolean containsValue(final Object code) {
+        if (code instanceof String) {
+            for (final Identifier identifier : identifiers) {
+                if (identifier != null && code.equals(identifier.getCode())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if at least one identifier declares the given
+     * {@linkplain Identifier#getAuthority() authority}.
+     *
+     * @param  authority The authority to search, which should be an instance of {@link Citation}.
+     * @return {@code true} if at least one identifier uses the given authority.
+     */
+    @Override
+    public boolean containsKey(final Object authority) {
+        return (authority instanceof Citation) && getIdentifier((Citation) authority) != null;
+    }
+
+    /**
+     * Returns the identifier for the given key, or {@code null} if none.
+     */
+    private Identifier getIdentifier(final Citation authority) {
+        for (final Identifier identifier : identifiers) {
+            if (identifier != null && Objects.equals(authority, identifier.getAuthority())) {
+                return identifier;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the identifier associated with the given authority,
+     * or {@code null} if no specialized identifier was found.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T getSpecialized(final IdentifierSpace<T> authority) {
+        final Identifier identifier = getIdentifier(authority);
+        return (identifier instanceof SpecializedIdentifier<?>) ? ((SpecializedIdentifier<T>) identifier).value : null;
+    }
+
+    /**
+     * Returns the code of the first identifier associated with the given
+     * {@linkplain Identifier#getAuthority() authority}, or {@code null}
+     * if no identifier was found.
+     *
+     * @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.
+     */
+    @Override
+    public String get(final Object authority) {
+        if (authority instanceof Citation) {
+            final Identifier identifier = getIdentifier((Citation) authority);
+            if (identifier != null) {
+                return identifier.getCode();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Removes all identifiers associated with the given
+     * {@linkplain Identifier#getAuthority() authority}.
+     *
+     * @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.
+     */
+    @Override
+    public String remove(final Object authority) {
+        return (authority instanceof Citation) ? put((Citation) authority, null) : null;
+    }
+
+    /**
+     * Sets the code of the identifier having the given authority to the given value.
+     * If no identifier is found for the given authority, a new one is created. If
+     * more than one identifier is found for the given authority, then all previous
+     * identifiers may be removed in order to ensure that the new entry will be the
+     * 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.
+     * @return The previous code for the given authority, or {@code null} if none.
+     */
+    @Override
+    public String put(final Citation authority, final String code) throws UnsupportedOperationException {
+        ArgumentChecks.ensureNonNull("authority", authority);
+        String old = null;
+        final Iterator<? extends Identifier> it = identifiers.iterator();
+        while (it.hasNext()) {
+            final Identifier identifier = it.next();
+            if (identifier == null) {
+                it.remove(); // Opportunist cleaning, but should not happen.
+            } else if (Objects.equals(authority, identifier.getAuthority())) {
+                if (code != null && identifier instanceof IdentifierMapEntry) {
+                    return ((IdentifierMapEntry) identifier).setValue(code);
+                    // No need to suppress other occurrences of the key (if any)
+                    // because we made a replacement in the first entry, so the
+                    // new value will be visible by the getter methods.
+                }
+                if (old == null) {
+                    old = identifier.getCode();
+                }
+                it.remove();
+                // Continue the iteration in order to remove all other occurrences,
+                // in order to ensure that the getter methods will see the new value.
+            }
+        }
+        if (code != null) {
+            identifiers.add(SpecializedIdentifier.parse(authority, code));
+        }
+        return old;
+    }
+
+    /**
+     * 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 {
+        ArgumentChecks.ensureNonNull("authority", authority);
+        T old = null;
+        final Iterator<? extends Identifier> it = identifiers.iterator();
+        while (it.hasNext()) {
+            final Identifier identifier = it.next();
+            if (identifier == null) {
+                it.remove(); // Opportunist cleaning, but should not happen.
+            } else if (Objects.equals(authority, identifier.getAuthority())) {
+                if (identifier instanceof SpecializedIdentifier<?>) {
+                    @SuppressWarnings("unchecked")
+                    final SpecializedIdentifier<T> id = (SpecializedIdentifier<T>) identifier;
+                    if (old == null) {
+                        old = id.value;
+                    }
+                    if (value != null) {
+                        id.value = value;
+                        return old;
+                        // No need to suppress other occurrences of the key (if any)
+                        // because we made a replacement in the first entry, so the
+                        // new value will be visible by the getter methods.
+                    }
+                }
+                it.remove();
+                // Continue the iteration in order to remove all other occurrences,
+                // in order to ensure that the getter methods will see the new value.
+            }
+        }
+        if (value != null) {
+            identifiers.add(new SpecializedIdentifier<>(authority, value));
+        }
+        return old;
+    }
+
+    /**
+     * Returns a view over the collection of identifiers. This view supports removal operation
+     * if the underlying collection of identifiers supports the {@link Iterator#remove()} method.
+     *
+     * @return A view over the collection of identifiers.
+     */
+    @Override
+    public synchronized Set<Entry<Citation,String>> entrySet() {
+        if (entries == null) {
+            entries = new Entries(identifiers);
+        }
+        return entries;
+    }
+
+    /**
+     * The view returned by {@link IdentifierMap#entrySet()}. If the backing identifier
+     * collection contains null entries, those entries will be ignored. If the backing
+     * collection contains many entries for the same authority, then only the first
+     * occurrence is retained.
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @since   0.3 (derived from geotk-3.18)
+     * @version 0.3
+     * @module
+     */
+    private static final class Entries extends AbstractSet<Entry<Citation,String>> {
+        /**
+         * The identifiers to wrap in a set of entries view. This is a reference
+         * to the same collection than {@link IdentifierMap#identifiers}.
+         */
+        private final Collection<? extends Identifier> identifiers;
+
+        /**
+         * Creates a new view over the collection of identifiers.
+         *
+         * @param identifiers The identifiers to wrap in a set of entries view.
+         */
+        Entries(final Collection<? extends Identifier> identifiers) {
+            this.identifiers = identifiers;
+        }
+
+        /**
+         * Same implementation than {@link IdentifierMap#clear()}.
+         */
+        @Override
+        public void clear() throws UnsupportedOperationException {
+            identifiers.clear();
+        }
+
+        /**
+         * Same implementation than {@link IdentifierMap#isEmpty()}.
+         */
+        @Override
+        public boolean isEmpty() {
+            return identifiers.isEmpty();
+        }
+
+        /**
+         * Counts the number of entries, ignoring null elements and duplicated authorities.
+         * Because {@code null} elements are ignored, this method may return 0 even if
+         * {@link #isEmpty()} returns {@code false}.
+         */
+        @Override
+        public int size() {
+            final HashMap<Citation,Boolean> done = new HashMap<>(hashMapCapacity(identifiers.size()));
+            for (final Identifier identifier : identifiers) {
+                if (identifier != null) {
+                    done.put(identifier.getAuthority(), null);
+                }
+            }
+            return done.size();
+        }
+
+        /**
+         * Returns an iterator over the (<var>citation</var>, <var>code</var>) entries.
+         */
+        @Override
+        public Iterator<Entry<Citation, String>> iterator() {
+            return new Iter(identifiers);
+        }
+    }
+
+    /**
+     * The iterator over the (<var>citation</var>, <var>code</var>) entries. This iterator is
+     * created by the {@link IdentifierMap.Entries} collection. It extends {@link HashMap} as
+     * an opportunist implementation strategy, but users don't need to know this detail.
+     * <p>
+     * This iterator supports the {@link #remove()} operation if the underlying collection
+     * supports it.
+     * <p>
+     * The map entries are used as a safety against duplicated authority values. The map values are
+     * non-null only after we iterated over an authority. Then the value is {@link Boolean#TRUE} if
+     * the identifier has been removed, of {@code Boolean#FALSE} otherwise.
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @since   0.3 (derived from geotk-3.18)
+     * @version 0.3
+     * @module
+     */
+    @SuppressWarnings("serial") // Not intended to be serialized.
+    private static final class Iter extends HashMap<Citation,Boolean> implements Iterator<Entry<Citation,String>> {
+        /**
+         * An iterator over the {@link IdentifierMap#identifiers} collection,
+         * or (@code null} if we have reached the iteration end.
+         */
+        private Iterator<? extends Identifier> identifiers;
+
+        /**
+         * The next entry to be returned by {@link #next()}, or {@code null} if not yet computed.
+         * This field will be computed only when {@link #next()} or {@link #hasNext()} is invoked.
+         */
+        private transient Entry<Citation,String> next;
+
+        /**
+         * The current authority. Used only for removal operations.
+         */
+        private transient Citation authority;
+
+        /**
+         * Creates a new iterator for the given collection of identifiers.
+         */
+        Iter(final Collection<? extends Identifier> identifiers) {
+            super(hashMapCapacity(identifiers.size()));
+            this.identifiers = identifiers.iterator();
+        }
+
+        /**
+         * Advances to the next non-null identifier, skips duplicated authorities, wraps the
+         * identifier in an entry if needed and stores the result in the {@link #next} field.
+         * If we reach the iteration end, then this method set the {@link #identifiers}
+         * iterator to {@code null}.
+         */
+        private void toNext() {
+            final Iterator<? extends Identifier> it = identifiers;
+            if (it != null) {
+                while (it.hasNext()) {
+                    final Identifier identifier = it.next();
+                    if (identifier != null) {
+                        final Citation authority = identifier.getAuthority();
+                        final Boolean state = put(authority, Boolean.FALSE);
+                        if (state == null) {
+                            if (identifier instanceof IdentifierMapEntry) {
+                                next = (IdentifierMapEntry) identifier;
+                            } else {
+                                next = new IdentifierMapEntry.Immutable(authority, identifier.getCode());
+                            }
+                            this.authority = authority;
+                            return;
+                        }
+                        if (state == Boolean.TRUE) {
+                            // Found a duplicated entry, and user asked for the
+                            // removal of that authority.
+                            it.remove();
+                        }
+                    }
+                }
+                identifiers = null;
+            }
+        }
+
+        /**
+         * If we need to search for the next element, fetches it now.
+         * Then returns {@code true} if we didn't reached the iteration end.
+         */
+        @Override
+        public boolean hasNext() {
+            if (next == null) {
+                toNext();
+            }
+            return identifiers != null;
+        }
+
+        /**
+         * If we need to search for the next element, searches it now. Then set {@link #next}
+         * to {@code null} as a flag meaning that next invocations will need to search again
+         * for an element, and returns the element that we got.
+         */
+        @Override
+        public Entry<Citation,String> next() throws NoSuchElementException {
+            Entry<Citation,String> entry = next;
+            if (entry == null) {
+                toNext();
+                entry = next;
+            }
+            next = null;
+            if (identifiers == null) {
+                throw new NoSuchElementException();
+            }
+            return entry;
+        }
+
+        /**
+         * Removes the last element returned by {@link #next()}. Note that if the {@link #next}
+         * field is non-null, that would mean that the iteration has moved since the last call
+         * to the {@link #next()} method, in which case the iterator is invalid.
+         */
+        @Override
+        public void remove() throws IllegalStateException {
+            final Iterator<? extends Identifier> it = identifiers;
+            if (it == null || next != null) {
+                throw new IllegalStateException();
+            }
+            it.remove();
+            put(authority, Boolean.TRUE);
+        }
+    }
+
+    /**
+     * Overrides the string representation in order to use only the authority title as keys.
+     * We do that because the string representations of {@code DefaultCitation} objects are
+     * very big.
+     */
+    @Override
+    public String toString() {
+	final StringBuilder buffer = new StringBuilder(100).append('{');
+	for (final Entry<Citation,String> entry : entrySet()) {
+	    if (buffer.length() != 1) {
+                buffer.append(", ");
+            }
+            SpecializedIdentifier.format(buffer, entry.getKey(), entry.getValue());
+	}
+        return buffer.append('}').toString();
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java?rev=1398723&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java Tue Oct 16 10:31:44 2012
@@ -0,0 +1,76 @@
+/*
+ * 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.internal.jaxb;
+
+import java.util.AbstractMap;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Citation;
+
+
+/**
+ * An entry in the {@link IdentifierMap}. This class implements both the
+ * {@link Map.Entry} interface (for inclusion in the set to be returned
+ * by {@link IdentifierMapAdapter#entrySet()}) and the {@link Identifier}
+ * interface (for inclusion in the {@link IdentifierMapAdapter#identifiers}
+ * collection).
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.18)
+ * @version 0.3
+ * @module
+ */
+final class IdentifierMapEntry extends AbstractMap.SimpleEntry<Citation,String> implements Identifier {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -5484541090753985572L;
+
+    /**
+     * Creates a new entry for the given authority and code.
+     */
+    IdentifierMapEntry(final Citation authority, final String code) {
+        super(authority, code);
+    }
+
+    /**
+     * Returns the identifier namespace, which is the key of this entry.
+     */
+    @Override
+    public Citation getAuthority() {
+        return getKey();
+    }
+
+    /**
+     * Returns the identifier code, which is the value of this entry.
+     */
+    @Override
+    public String getCode() {
+        return getValue();
+    }
+
+    /**
+     * Same than the above, but as an immutable entry. We use this implementation when the
+     * entry has been created on-the-fly at iteration time rather than being stored in the
+     * identifier collection.
+     */
+    static final class Immutable extends AbstractMap.SimpleImmutableEntry<Citation,String> implements Identifier {
+        private static final long serialVersionUID = -8179498861233498041L;
+        Immutable(Citation authority, String code) {super(authority, code);}
+        @Override public Citation getAuthority()   {return getKey();}
+        @Override public String   getCode()        {return getValue();}
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Added: 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=1398723&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java Tue Oct 16 10:31:44 2012
@@ -0,0 +1,205 @@
+/*
+ * 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.internal.jaxb;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Citation;
+
+import org.apache.sis.xml.IdentifierSpace;
+import org.apache.sis.xml.XLink;
+
+
+/**
+ * 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 #isSpecialCase(Citation)} for spotting the code where
+ * a special handling is applied.
+ * <p>
+ * <ul>
+ *   <li>{@link IdentifierSpace#HREF}, handled as a shortcut to {@link XLink#getHRef()}.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.19)
+ * @version 0.3
+ * @module
+ */
+final class IdentifierMapWithSpecialCases extends IdentifierMapAdapter {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 5139573827448780289L;
+
+    /**
+     * Creates a new map which will be a view over the given identifiers.
+     *
+     * @param identifiers The identifiers to wrap in a map view.
+     */
+    public IdentifierMapWithSpecialCases(final Collection<Identifier> identifiers) {
+        super(identifiers);
+    }
+
+    /**
+     * 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.
+     */
+    private static int specialCase(final Object authority) {
+        if (authority == IdentifierSpace.HREF) return NonMarshalledAuthority.HREF;
+        // A future Apache SIS version may add more special cases here.
+        return -1;
+    }
+
+    /**
+     * Extracts the {@code xlink:href} value from the {@link XLink} if presents. This method does
+     * not test if an explicit {@code xlink:href} identifier exists - this check must be done by
+     * the caller <strong>before</strong> to invoke this method, by invoking one of the getter
+     * methods defined in the {@link IdentifierMapAdapter} super-class.
+     */
+    private String getHRef() {
+        final XLink link = super.getSpecialized(IdentifierSpace.XLINK);
+        if (link != null) {
+            final URI href = link.getHRef();
+            if (href != null) {
+                return href.toString();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets the {@code xlink:href} value, which may be null. If an explicit {@code xlink:href}
+     * identifier exists, it is removed before to set the new {@code href} in the {@link XLink}
+     * object.
+     */
+    private URI setHRef(final URI href) {
+        super.putSpecialized(IdentifierSpace.HREF, null);
+        XLink link = super.getSpecialized(IdentifierSpace.XLINK);
+        if (link != null) {
+            final URI old = link.getHRef();
+            link.setHRef(href);
+            return old;
+        }
+        if (href != null) {
+            link = new XLink();
+            link.setHRef(href);
+            super.putSpecialized(IdentifierSpace.XLINK, link);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean containsValue(final Object code) {
+        return super.containsValue(code) || Objects.equals(code, getHRef());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean containsKey(final Object authority) {
+        if (super.containsKey(authority)) {
+            return true;
+        }
+        switch (specialCase(authority)) {
+            case NonMarshalledAuthority.HREF: {
+                final XLink link = super.getSpecialized(IdentifierSpace.XLINK);
+                return (link != null) && (link.getHRef() != null);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T getSpecialized(final IdentifierSpace<T> authority) {
+        T value = super.getSpecialized(authority);
+        if (value == null) {
+            switch (specialCase(authority)) {
+                case NonMarshalledAuthority.HREF: {
+                    final XLink link = super.getSpecialized(IdentifierSpace.XLINK);
+                    if (link != null) {
+                        value = (T) link.getHRef();
+                    }
+                    break;
+                }
+            }
+        }
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String get(final Object authority) {
+        String value = super.get(authority);
+        if (value == null) {
+            switch (specialCase(authority)) {
+                case NonMarshalledAuthority.HREF: {
+                    value = getHRef();
+                    break;
+                }
+            }
+        }
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String put(final Citation authority, final String code) throws UnsupportedOperationException {
+        switch (specialCase(authority)) {
+            case NonMarshalledAuthority.HREF: {
+                try {
+                    final URI old = setHRef((code != null) ? new URI(code) : null);
+                    return (old != null) ? old.toString() : null;
+                } catch (URISyntaxException e) {
+                    // Do not log the exception, since it will be
+                    // reported by super.put(Citation, String).
+                }
+                break;
+            }
+        }
+        return super.put(authority, code);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T putSpecialized(final IdentifierSpace<T> authority, final T value) throws UnsupportedOperationException {
+        switch (specialCase(authority)) {
+            case NonMarshalledAuthority.HREF: {
+                return (T) setHRef((URI) value);
+            }
+        }
+        return super.putSpecialized(authority, value);
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Added: 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=1398723&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java Tue Oct 16 10:31:44 2012
@@ -0,0 +1,218 @@
+/*
+ * 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.internal.jaxb;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.UUID;
+import java.io.Serializable;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Citation;
+import org.apache.sis.xml.XLink;
+import org.apache.sis.xml.IdentifierMap;
+import org.apache.sis.xml.IdentifierSpace;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.internal.util.Citations;
+
+// Related to JDK7
+import java.util.Objects;
+
+
+/**
+ * Wraps a {@link XLink}, {@link URI} or {@link UUID} as an identifier in the {@link IdentifierMap}.
+ * The {@linkplain #authority} is typically an instance of {@link NonMarshalledAuthority}. The value
+ * is an object of a type constrained by the authority.
+ *
+ * @param  <T> The value type, typically {@link XLink}, {@link UUID} or {@link String}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.19)
+ * @version 0.3
+ * @module
+ */
+public final class SpecializedIdentifier<T> implements Identifier, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 1673231050676950993L;
+
+    /**
+     * The authority, typically as a {@link NonMarshalledAuthority) instance.
+     * Null value is not recommended, but this {@code SpecializedIdentifier}
+     * is tolerant to such cases.
+     *
+     * @see #getAuthority()
+     */
+    private final IdentifierSpace<T> authority;
+
+    /**
+     * The identifier value. The identifier {@linkplain #getCode() code} will be the
+     * {@linkplain Object#toString() string representation} of this value, if non-null.
+     *
+     * <p>This value is set at construction time, but may be modified later by
+     * {@link IdentifierMapAdapter#putSpecialized(IdentifierSpace, Object)}.</p>
+     *
+     * @see #getValue()
+     * @see #getCode()
+     */
+    T value;
+
+    /**
+     * Creates a new adapter for the given authority and identifier value.
+     *
+     * @param authority The identifier authority.
+     * @param value The identifier value, or {@code null} if not yet defined.
+     */
+    public SpecializedIdentifier(final IdentifierSpace<T> authority, final T value) {
+        this.authority = authority;
+        this.value = value;
+    }
+
+    /**
+     * Creates an identifier from a text value. This method creates an instance of
+     * {@code SpecializedIdentifier} if the given authority is one of the "special"
+     * authorities declared in the {@link IdentifierSpace} interface. Otherwise a
+     * plain {@link IdentifierMapEntry} is created.
+     *
+     * @param authority The authority, typically as one of the {@link IdentifierSpace} constants.
+     * @param code The identifier code to parse.
+     *
+     * @see IdentifierMapAdapter#put(Citation, String)
+     */
+    static Identifier parse(final Citation authority, final String code) {
+        if (authority instanceof NonMarshalledAuthority) {
+            switch (((NonMarshalledAuthority) authority).ordinal) {
+                case NonMarshalledAuthority.ID: {
+                    return new SpecializedIdentifier<>(IdentifierSpace.ID, code);
+                }
+                case NonMarshalledAuthority.UUID: {
+                    try {
+                        return new SpecializedIdentifier<>(IdentifierSpace.UUID, UUID.fromString(code));
+                    } catch (IllegalArgumentException e) {
+                        parseFailure(e);
+                        break;
+                    }
+                }
+                case NonMarshalledAuthority.HREF: {
+                    final URI href;
+                    try {
+                        href = new URI(code);
+                    } catch (URISyntaxException e) {
+                        parseFailure(e);
+                        break;
+                    }
+                    return new SpecializedIdentifier<>(IdentifierSpace.HREF, href);
+                }
+                case NonMarshalledAuthority.XLINK: {
+                    final URI href;
+                    try {
+                        href = new URI(code);
+                    } catch (URISyntaxException e) {
+                        parseFailure(e);
+                        break;
+                    }
+                    final XLink xlink = new XLink();
+                    xlink.setHRef(href);
+                    return new SpecializedIdentifier<>(IdentifierSpace.XLINK, xlink);
+                }
+            }
+        }
+        return new IdentifierMapEntry(authority, code);
+    }
+
+    /**
+     * Invoked by {@link #parse(Citation,String)} when a string can not be parsed.
+     * 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) {
+        // IdentifierMap.put(Citation,String) is the public facade.
+        Logging.recoverableException(IdentifierMap.class, "put", e);
+    }
+
+    /**
+     * Returns the authority specified at construction time.
+     */
+    @Override
+    public Citation getAuthority() {
+        return authority;
+    }
+
+    /**
+     * Returns the identifier value. This is the {@linkplain #getCode() code} expressed as
+     * an object more specialized than {@link String}.
+     *
+     * @return The identifier value, or {@code null} if none.
+     */
+    public T getValue() {
+        return value;
+    }
+
+    /**
+     * Returns a string representation of the {@linkplain #getValue() identifier value},
+     * or {@code null} if none.
+     */
+    @Override
+    public String getCode() {
+        final T value = this.value;
+        return (value != null) ? value.toString() : null;
+    }
+
+    /**
+     * Returns a hash code value for this identifier.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(value) + 31 * Objects.hashCode(authority);
+    }
+
+    /**
+     * Compares this identifier with the given object for equality.
+     *
+     * @param other The object to compare with this identifier for equality.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other instanceof SpecializedIdentifier<?>) {
+            final SpecializedIdentifier<?> that = (SpecializedIdentifier<?>) other;
+            return Objects.equals(authority, that.authority) &&
+                   Objects.equals(value, that.value);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a string representation of this identifier.
+     */
+    @Override
+    public String toString() {
+        final StringBuilder buffer = new StringBuilder("Identifier[");
+        format(buffer, authority, getCode());
+        return buffer.append(']').toString();
+    }
+
+    /**
+     * Formats the given (authority, code) par value in the given buffer.
+     */
+    static void format(final StringBuilder buffer, final Citation authority, final String code) {
+        buffer.append(Citations.getIdentifier(authority)).append('=');
+        final boolean quote = (code != null) && (code.indexOf('[') < 0);
+        if (quote) buffer.append('“');
+        buffer.append(code);
+        if (quote) buffer.append('”');
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java?rev=1398723&r1=1398722&r2=1398723&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java Tue Oct 16 10:31:44 2012
@@ -23,6 +23,15 @@
  * This package is for internal use by SIS only. Classes in this package
  * may change in incompatible ways in any future version without notice.
  *
+ * {@section Main content}
+ * {@link org.apache.sis.internal.jaxb.IdentifierMapAdapter} is our internal implementation of
+ * the public {@link org.apache.sis.xml.IdentifierMap} interface. The actual implementation is
+ * usually the {@code IdentifierMapWithSpecialCases} subclass.
+ *
+ * <p>{@link org.apache.sis.internal.jaxb.SpecializedIdentifier} wraps {@link org.apache.sis.xml.XLink},
+ * {@link java.net.URI} and {@link java.util.UUID} as {@link org.opengis.metadata.Identifier} instances.
+ * This is used for storing the value in a list of identifiers while preserving the original object.</p>
+ *
  * @author  Cédric Briançon (Geomatys)
  * @since   0.3 (derived from geotk-3.00)
  * @version 0.3

Added: 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=1398723&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java (added)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java Tue Oct 16 10:31:44 2012
@@ -0,0 +1,235 @@
+/*
+ * 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.internal.jaxb;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Collection;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Citation;
+import org.apache.sis.xml.IdentifierMap;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static java.util.UUID.fromString;
+import static org.apache.sis.test.Assert.*;
+import static org.apache.sis.xml.IdentifierSpace.*;
+
+
+/**
+ * Tests {@link IdentifierMapAdapter}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.18)
+ * @version 0.3
+ * @module
+ */
+public strictfp class IdentifierMapAdapterTest extends TestCase {
+    /**
+     * Creates the {@link IdentifierMapAdapter} instance to test for the given identifiers.
+     * Subclasses will override this method.
+     *
+     * @param  identifiers The identifiers to wrap in an {@code IdentifierMapAdapter}.
+     * @return The {@code IdentifierMapAdapter} to test.
+     */
+    IdentifierMapAdapter create(final Collection<Identifier> identifiers) {
+        return new IdentifierMapAdapter(identifiers);
+    }
+
+    /**
+     * Asserts that the content of the given map is equals to the given content, represented
+     * as a string. Subclasses can override this method in order to alter the expected string.
+     *
+     * @param  expected The expected content.
+     * @return The map to compare with the expected content.
+     */
+    void assertMapEquals(final String expected, final Map<Citation,String> map) {
+        assertEquals(expected, map.toString());
+    }
+
+    /**
+     * Returns a string representation of the given {@code href} value.
+     * The default implementation returns the value unchanged.
+     */
+    String toHRefString(final String href) {
+        return href;
+    }
+
+    /**
+     * Tests read and write operations on an {@link IdentifierMapAdapter}, using a well-formed
+     * identifier collection (no null values, no duplicated authorities).
+     * <p>
+     * This test does not use the {@link IdentifierMap}-specific API.
+     */
+    @Test
+    public void testGetAndPut() {
+        final List<Identifier> identifiers = new ArrayList<>();
+        final Map<Citation,String> map = create(identifiers);
+        assertTrue(map.isEmpty());
+        assertEquals(0, map.size());
+
+        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"));
+        assertMapEquals("{gml:id=“myID”, gco:uuid=“myUUID”}", map);
+
+        assertEquals("myUUID", map.put(UUID, "myNewUUID"));
+        assertFalse (map.containsValue("myUUID"));
+        assertTrue  (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));
+        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));
+        assertMapEquals("{gml:id=“myID”, xlink:href=“myHREF”}", map);
+        assertEquals(2, map.size());
+        assertEquals(2, identifiers.size());
+
+        assertTrue  (map.values().remove(toHRefString("myHREF")));
+        assertFalse (map.containsValue("myHREF"));
+        assertFalse (map.containsKey(HREF));
+        assertMapEquals("{gml:id=“myID”}", map);
+        assertEquals(1, map.size());
+        assertEquals(1, identifiers.size());
+
+        assertTrue  (map.keySet().remove(ID));
+        assertFalse (map.containsValue("myID"));
+        assertFalse (map.containsKey(ID));
+        assertMapEquals("{}", map);
+        assertEquals(0, map.size());
+        assertEquals(0, identifiers.size());
+    }
+
+    /**
+     * Tests write operations on an {@link IdentifierMap} using specific API.
+     */
+    @Test
+    public void testPutSpecialized() {
+        final List<Identifier> identifiers = new ArrayList<>();
+        final IdentifierMap map = create(identifiers);
+        final String myID = "myID";
+        final java.util.UUID myUUID = fromString("a1eb6e53-93db-4942-84a6-d9e7fb9db2c7");
+        final URI myURI = URI.create("http://mylink");
+
+        map.putSpecialized(ID,   myID);
+        map.putSpecialized(UUID, myUUID);
+        map.putSpecialized(HREF, myURI);
+        assertMapEquals("{gml:id=“myID”,"
+                + " gco:uuid=“a1eb6e53-93db-4942-84a6-d9e7fb9db2c7”,"
+                + " xlink:href=“http://mylink”}", map);
+
+        assertSame(myID,   map.getSpecialized(ID));
+        assertSame(myUUID, map.getSpecialized(UUID));
+        assertSame(myURI,  map.getSpecialized(HREF));
+        assertEquals("myID",                                 map.get(ID));
+        assertEquals("a1eb6e53-93db-4942-84a6-d9e7fb9db2c7", map.get(UUID));
+        assertEquals("http://mylink",                        map.get(HREF));
+    }
+
+    /**
+     * Tests read operations on an {@link IdentifierMap} using specific API.
+     */
+    @Test
+    public void testGetSpecialized() {
+        final List<Identifier> identifiers = new ArrayList<>();
+        final IdentifierMap map = create(identifiers);
+
+        map.put(ID,   "myID");
+        map.put(UUID, "a1eb6e53-93db-4942-84a6-d9e7fb9db2c7");
+        map.put(HREF, "http://mylink");
+        assertMapEquals("{gml:id=“myID”,"
+                + " gco:uuid=“a1eb6e53-93db-4942-84a6-d9e7fb9db2c7”,"
+                + " xlink:href=“http://mylink”}", map);
+
+        assertEquals("myID",                                             map.get           (ID));
+        assertEquals("a1eb6e53-93db-4942-84a6-d9e7fb9db2c7",             map.get           (UUID));
+        assertEquals("http://mylink",                                    map.get           (HREF));
+        assertEquals("myID",                                             map.getSpecialized(ID));
+        assertEquals(URI.create("http://mylink"),                        map.getSpecialized(HREF));
+        assertEquals(fromString("a1eb6e53-93db-4942-84a6-d9e7fb9db2c7"), map.getSpecialized(UUID));
+    }
+
+    /**
+     * Tests the handling of duplicated authorities.
+     */
+    @Test
+    public void testDuplicatedAuthorities() {
+        final List<Identifier> identifiers = new ArrayList<>();
+        identifiers.add(new IdentifierMapEntry(ID,   "myID1"));
+        identifiers.add(new IdentifierMapEntry(UUID, "myUUID"));
+        identifiers.add(new IdentifierMapEntry(ID,   "myID2"));
+
+        final IdentifierMap map = create(identifiers);
+        assertEquals("Duplicated authorities shall be filtered.", 2, map.size());
+        assertEquals("Duplicated authorities shall still exist.", 3, identifiers.size());
+        assertEquals("myID1",  map.get(ID));
+        assertEquals("myUUID", map.get(UUID));
+
+        final Iterator<Citation> it = map.keySet().iterator();
+        assertTrue(it.hasNext());
+        assertSame(ID, it.next());
+        it.remove();
+        assertTrue(it.hasNext());
+        assertSame(UUID, it.next());
+        assertFalse("Duplicated authority shall have been removed.", it.hasNext());
+
+        assertEquals(1, identifiers.size());
+        assertEquals(1, map.size());
+    }
+
+    /**
+     * Tests serialization.
+     */
+    @Test
+    public void testSerialization() {
+        assertSame(ID,   assertSerializedEquals(ID));
+        assertSame(UUID, assertSerializedEquals(UUID));
+        assertSame(HREF, assertSerializedEquals(HREF));
+
+        final List<Identifier> identifiers = new ArrayList<>();
+        final Map<Citation,String> map = create(identifiers);
+        identifiers.add(new IdentifierMapEntry(ID,   "myID"));
+        identifiers.add(new IdentifierMapEntry(UUID, "myUUID"));
+
+        final Map<Citation,String> copy = assertSerializedEquals(map);
+        assertNotSame(map, copy);
+        assertEquals(2, copy.size());
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: 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=1398723&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java (added)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java Tue Oct 16 10:31:44 2012
@@ -0,0 +1,109 @@
+/*
+ * 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.internal.jaxb;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Citation;
+import org.apache.sis.xml.IdentifierMap;
+import org.apache.sis.xml.IdentifierSpace;
+import org.apache.sis.xml.XLink;
+import org.apache.sis.test.DependsOn;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.apache.sis.test.TestUtilities.getSingleton;
+
+
+/**
+ * Tests {@link IdentifierMapWithSpecialCases}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.19)
+ * @version 0.3
+ * @module
+ */
+@DependsOn(IdentifierMapAdapter.class)
+public final strictfp class IdentifierMapWithSpecialCasesTest extends IdentifierMapAdapterTest {
+    /**
+     * The HREF string to replace by {@link XLink#toString()}.
+     */
+    private static final String TO_REPLACE = "xlink:href=“";
+
+    /**
+     * Creates the {@link IdentifierMapAdapter} instance to test for the given identifiers.
+     *
+     * @param  identifiers The identifiers to wrap in an {@code IdentifierMapAdapter}.
+     * @return The {@code IdentifierMapAdapter} to test.
+     */
+    @Override
+    IdentifierMapAdapter create(final Collection<Identifier> identifiers) {
+        return new IdentifierMapWithSpecialCases(identifiers);
+    }
+
+    /**
+     * Replaces the {@code xlink:href} value by the {@link XLink#toString()} value
+     * before to compare with the map content.
+     */
+    @Override
+    void assertMapEquals(String expected, final Map<Citation,String> map) {
+        final int start = expected.indexOf(TO_REPLACE);
+        if (start >= 0) {
+            final int end = start + TO_REPLACE.length();
+            final int close = expected.indexOf('”', end);
+            final StringBuilder buffer = new StringBuilder(expected);
+            buffer.replace(close, close+1, "\"]");
+            buffer.replace(start, end, "xlink=XLink[href=\"");
+            expected = buffer.toString();
+        }
+        super.assertMapEquals(expected, map);
+    }
+
+    /**
+     * Wraps the given {@code href} value in a {@link XLink} string representation.
+     */
+    @Override
+    String toHRefString(final String href) {
+        return "XLink[href=\"" + href + "\"]";
+    }
+
+    // Inherits all test methods from the super class.
+
+    /**
+     * Tests explicitely the special handling of {@code href} values.
+     */
+    @Test
+    public void testSpecialCases() {
+        final List<Identifier> identifiers = new ArrayList<>();
+        final IdentifierMap map = create(identifiers);
+        map.put(IdentifierSpace.HREF, "myHREF");
+        assertEquals("myHREF", map.get(IdentifierSpace.HREF));
+
+        // Check the XLink object
+        final XLink link = map.getSpecialized(IdentifierSpace.XLINK);
+        assertEquals("myHREF", String.valueOf(link.getHRef()));
+        assertEquals(link.toString(), getSingleton(identifiers).getCode());
+
+        // Modidfy the XLink object directly
+        link.setHRef(URI.create("myNewHREF"));
+        assertEquals("myNewHREF", map.get(IdentifierSpace.HREF));
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java?rev=1398723&r1=1398722&r2=1398723&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java (original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java Tue Oct 16 10:31:44 2012
@@ -19,6 +19,7 @@ package org.apache.sis.test;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
+import java.util.Iterator;
 import java.util.concurrent.Callable;
 import java.lang.reflect.UndeclaredThrowableException;
 import java.text.DateFormat;
@@ -96,6 +97,23 @@ public final strictfp class TestUtilitie
     }
 
     /**
+     * Returns the single element from the given collection. If the given collection is null
+     * or does not contains exactly one element, then an {@link AssertionError} is thrown.
+     *
+     * @param  <E> The type of collection elements.
+     * @param  collection The collection from which to get the singleton.
+     * @return The singleton element from the collection.
+     */
+    public static <E> E getSingleton(final Iterable<? extends E> collection) {
+        assertNotNull("Null collection.", collection);
+        final Iterator<? extends E> it = collection.iterator();
+        assertTrue("The collection is empty.", it.hasNext());
+        final E element = it.next();
+        assertFalse("The collection has more than one element.", it.hasNext());
+        return element;
+    }
+
+    /**
      * If the given failure is not null, re-thrown it as an {@link Error} or
      * {@link RuntimeException}. Otherwise do nothing.
      *

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1398723&r1=1398722&r2=1398723&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java (original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java Tue Oct 16 10:31:44 2012
@@ -49,7 +49,9 @@ import org.junit.runners.Suite;
   org.apache.sis.util.collection.WeakHashSetTest.class,
   org.apache.sis.util.collection.WeakValueHashMapTest.class,
   org.apache.sis.util.collection.CacheTest.class,
-  org.apache.sis.xml.XLinkTest.class
+  org.apache.sis.xml.XLinkTest.class,
+  org.apache.sis.internal.jaxb.IdentifierMapAdapterTest.class,
+  org.apache.sis.internal.jaxb.IdentifierMapWithSpecialCasesTest.class
 })
 public final strictfp class UtilityTestSuite extends TestSuite {
 }



Mime
View raw message