sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1639971 - in /sis/branches/JDK8/core/sis-feature/src: main/java/org/apache/sis/feature/ test/java/org/apache/sis/feature/ test/java/org/apache/sis/test/suite/
Date Sun, 16 Nov 2014 06:40:24 GMT
Author: desruisseaux
Date: Sun Nov 16 06:40:24 2014
New Revision: 1639971

URL: http://svn.apache.org/r1639971
Log:
First draft of DefaultAttribute.characteristics() method.

Added:
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
  (with props)
Modified:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java?rev=1639971&r1=1639970&r2=1639971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
[UTF-8] Sun Nov 16 06:40:24 2014
@@ -16,7 +16,9 @@
  */
 package org.apache.sis.feature;
 
+import java.util.Map;
 import java.util.Collection;
+import java.util.Collections;
 import java.io.Serializable;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.quality.DataQuality;
@@ -71,6 +73,13 @@ public abstract class AbstractAttribute<
     final AttributeType<V> type;
 
     /**
+     * Other attributes that describes this attribute, or {@code null} if not yet created.
+     *
+     * @see #characteristics()
+     */
+    private transient Map<String,Attribute<?>> characteristics;
+
+    /**
      * Creates a new attribute of the given type.
      *
      * @param type Information about the attribute (base Java class, domain of values, <i>etc.</i>).
@@ -192,6 +201,95 @@ public abstract class AbstractAttribute<
     }
 
     /**
+     * Other attributes that describes this attribute. For example if this attribute carries
a measurement,
+     * then a characteristic of this attribute could be the measurement accuracy.
+     * See "<cite>Attribute characterization</cite>" in {@link DefaultAttributeType}
Javadoc for more information.
+     *
+     * <p>The map returned by this method contains only the characteristics explicitely
defined for this attribute.
+     * If the map contains no characteristic for a given name, a {@linkplain DefaultAttributeType#getDefaultValue()
+     * default value} may still exist.
+     * In such cases, callers may also need to inspect the {@link DefaultAttributeType#characteristics()}
+     * as shown in the <cite>Reading a characteristic</cite> section below.</p>
+     *
+     * <div class="note"><b>Rational:</b>
+     * Very often, all attributes of a given type in the same file have the same characteristics.
+     * For example it is very common that all temperature measurements in a file have the
same accuracy,
+     * and setting a different accuracy for a single measurement is relatively rare.
+     * Consequently, {@code characteristics.isEmpty()} is a convenient way to check that
an attribute have
+     * all the "standard" characteristics and need no special processing.</div>
+     *
+     * {@section Reading a characteristic}
+     * If an attribute is known to be a measurement with a characteristic named "accuracy"
of type {@link Float},
+     * then the accuracy value could be read as below:
+     *
+     * {@preformat java
+     *     Float getAccuracy(Attribute<?> measurement) {
+     *         Attribute<?> accuracy = measurement.characteristics().get("accuracy");
+     *         if (accuracy != null) {
+     *             return (Float) accuracy.getValue(); // Value may be null.
+     *         } else {
+     *             return (Float) measurement.getType().characteristics().get("accuracy").getDefaultValue();
+     *             // A more sophisticated implementation would probably cache the default
value somewhere.
+     *         }
+     *     }
+     * }
+     *
+     * {@section Adding a characteristic}
+     * A new characteristic can be added in the map in three different ways:
+     * <ol>
+     *   <li>Putting the (<var>name</var>, <var>characteristic</var>)
pair explicitely.
+     *     If an older characteristic existed for that name, it will be replaced.
+     *     Example:
+     *
+     *     {@preformat java
+     *       Attribute<?> accuracy = ...; // To be created by the caller.
+     *       characteristics.put("accuracy", accuracy);
+     *     }</li>
+     *
+     *   <li>Adding the new characteristic to the {@linkplain Map#values() values}
collection.
+     *     The name is inferred automatically from the characteristic type.
+     *     If an older characteristic existed for the same name, an {@link IllegalStateException}
will be thrown.
+     *     Example:
+     *
+     *     {@preformat java
+     *       Attribute<?> accuracy = ...; // To be created by the caller.
+     *       characteristics.values().add(accuracy);
+     *     }</li>
+     *
+     *   <li>Adding the characteristic name to the {@linkplain Map#keySet() key set}.
+     *     If no characteristic existed for that name, a default one will be created.
+     *     Example:
+     *
+     *     {@preformat java
+     *       characteristics.keySet().add("accuracy"); // Ensure that an entry will exist
for that name.
+     *       Attribute<?> accuracy = characteristics.get("accuracy");
+     *       Features.cast(accuracy, Float.class).setValue(...); // Set new accuracy value
here as a float.
+     *     }</li>
+     * </ol>
+     *
+     * @return Other attribute types that describes this attribute type, or an empty set
if none.
+     *
+     * @see DefaultAttributeType#characteristics()
+     */
+    public Map<String,Attribute<?>> characteristics() {
+        if (characteristics == null) {
+            if (type instanceof DefaultAttributeType<?>) {
+                Map<String, AttributeType<?>> map = ((DefaultAttributeType<?>)
type).characteristics();
+                if (map.isEmpty()) {
+                    characteristics = Collections.emptyMap();
+                } else {
+                    if (!(map instanceof CharacteristicTypeMap)) {
+                        final Collection<AttributeType<?>> types = map.values();
+                        map = CharacteristicTypeMap.create(type, types.toArray(new AttributeType<?>[types.size()]));
+                    }
+                    characteristics = new CharacteristicMap(this, (CharacteristicTypeMap)
map);
+                }
+            }
+        }
+        return characteristics;
+    }
+
+    /**
      * Evaluates the quality of this attribute at this method invocation time. The data quality
reports
      * may include information about whether the attribute value mets the constraints defined
by the
      * {@linkplain DefaultAttributeType attribute type}, or any other criterion at implementation
choice.
@@ -280,17 +378,22 @@ public abstract class AbstractAttribute<
     /**
      * Returns a copy of this attribute.
      * The default implementation returns a <em>shallow</em> copy:
-     * the attribute {@linkplain #getValue() value} is <strong>not</strong> cloned.
+     * the attribute {@linkplain #getValue() value} and {@linkplain #characteristics() characteristics}
+     * are <strong>not</strong> cloned.
      * However subclasses may choose to do otherwise.
      *
      * @return A clone of this attribute.
-     * @throws CloneNotSupportedException if this attribute can not be cloned.
-     *         The default implementation never throw this exception. However subclasses
may throw it,
-     *         for example on attempt to clone the attribute value.
+     * @throws CloneNotSupportedException if this attribute, the {@linkplain #getValue()
value}
+     *         or one of its {@linkplain #characteristics() characteristics} can not be cloned.
      */
     @Override
     @SuppressWarnings("unchecked")
     public AbstractAttribute<V> clone() throws CloneNotSupportedException {
-        return (AbstractAttribute<V>) super.clone();
+        final AbstractAttribute<V> clone = (AbstractAttribute<V>) super.clone();
+        final Map<String,Attribute<?>> c = clone.characteristics;
+        if (c instanceof CharacteristicMap) {
+            clone.characteristics = ((CharacteristicMap) c).clone();
+        }
+        return clone;
     }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java?rev=1639971&r1=1639970&r2=1639971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
[UTF-8] Sun Nov 16 06:40:24 2014
@@ -17,14 +17,12 @@
 package org.apache.sis.feature;
 
 import java.util.Map;
-import java.util.Set;
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.util.Cloner;
+import org.apache.sis.internal.util.AbstractMap;
 import org.apache.sis.internal.util.AbstractMapEntry;
 
 // Branch-dependent imports
@@ -35,23 +33,12 @@ import org.opengis.feature.Attribute;
  * Implementation of {@link AbstractAttribute#characteristics()} map.
  * This map holds only the attribute characteristics which have been explicitely set or requested.
  *
- * <p>This implementation has one behavioral difference compared to the familiar {@code
Map} invariants:
- * a call to the {@link #get(Object)} method creates a new {@code Attribute} instance if
the given key
- * is valid and no instance existed previously for that key, thus increasing this {@code
Map} size by one.
- * If this behavior is not desired, then caller should check {@link #containsKey(Object)}
first.</p>
- *
- * <div class="note"><b>Note:</b>
- * Such departures are unusual, but exists also in the JDK. For example {@link java.util.WeakHashMap}
too
- * may change its size as a result of read-only methods. Those maps behave as though an unknown
thread is
- * silently adding or removing entries.
- * </div>
- *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
  * @version 0.5
  * @module
  */
-final class CharacteristicMap extends AbstractMap<String,Attribute<?>> {
+final class CharacteristicMap extends AbstractMap<String,Attribute<?>> implements
Cloneable {
     /**
      * The attribute source for which to provide characteristics.
      */
@@ -79,6 +66,41 @@ final class CharacteristicMap extends Ab
     }
 
     /**
+     * Returns a copy of this map. Characteristics are also cloned.
+     *
+     * @return A copy of this map.
+     */
+    @Override
+    public CharacteristicMap clone() throws CloneNotSupportedException {
+        final CharacteristicMap clone = (CharacteristicMap) super.clone();
+        Attribute<?>[] c = clone.characterizedBy;
+        if (c != null) {
+            clone.characterizedBy = c = c.clone();
+            final Cloner cloner = new Cloner();
+            for (int i=0; i<c.length; i++) {
+                final Attribute<?> attribute = c[i];
+                if (attribute instanceof Cloneable) {
+                    c[i] = (Attribute<?>) cloner.clone(attribute);
+                }
+            }
+        }
+        return clone;
+    }
+
+    /**
+     * Removes all entries in this map.
+     */
+    @Override
+    public void clear() {
+        /*
+         * Implementation note: We could keep existing array and clear it with Arrays.fill(characterizedBy,
null)
+         * instead, but maybe the user does not plan to store characteristics anymore for
the attribute. Setting
+         * the array reference to null free more memory in such cases.
+         */
+        characterizedBy = null;
+    }
+
+    /**
      * Returns {@code false} if this map contains at least one characteristic.
      */
     @Override
@@ -110,44 +132,17 @@ final class CharacteristicMap extends Ab
     }
 
     /**
-     * Returns {@code true} if this map contains an attribute characteristic for the given
name.
+     * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public boolean containsKey(final Object key) {
+    public Attribute<?> get(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
-                return characterizedBy[index] != null;
+                return characterizedBy[index];
             }
         }
-        return false;
-    }
-
-    /**
-     * Returns the attribute characteristic for the given name, or {@code null} if none.
If no characteristic
-     * exist for the given key and that key is valid, then this method will silently creates
a new instance.
-     * The intend is to allow callers to set a characteristic values using the following
pattern:
-     *
-     * {@preformat java
-     *     Attribute<?> characteristic = attribute.characteristics().get("accuracy");
-     *     Features.cast(characteristic, Double.class).setValue(0.1);
-     * }
-     */
-    @Override
-    public Attribute<?> get(final Object key) {
-        final Integer index = types.indices.get(key);
-        if (index == null) {
-            return null;
-        }
-        if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
-        }
-        Attribute<?> attribute = characterizedBy[index];
-        if (attribute == null) {
-            attribute = AbstractAttribute.create(types.characterizedBy[index]);
-            characterizedBy[index] = attribute;
-        }
-        return attribute;
+        return null;
     }
 
     /**
@@ -167,15 +162,31 @@ final class CharacteristicMap extends Ab
     }
 
     /**
-     * Sets the attribute characteristic for the given name.
+     * Returns the index for the characteristic of the given name.
+     *
+     * @param  key The name for which to get the characteristic index.
+     * @return The index for the characteristic of the given name.
+     * @throws IllegalArgumentException if the given key is not the name of a characteristic
in this map.
      */
-    @Override
-    public Attribute<?> put(final String key, final Attribute<?> value) {
-        ArgumentChecks.ensureNonNull("value", value);
+    private int indexOf(final String key) {
+        ArgumentChecks.ensureNonNull("key", key);
         final Integer index = types.indices.get(key);
         if (index == null) {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.PropertyNotFound_2,
source.getName(), key));
         }
+        return index;
+    }
+
+    /**
+     * Sets the attribute characteristic for the given name.
+     *
+     * @param  key The name of the characteristic to set.
+     * @throws IllegalArgumentException if the given key is not the name of a characteristic
in this map.
+     */
+    @Override
+    public Attribute<?> put(final String key, final Attribute<?> value) {
+        final int index = indexOf(key);
+        ArgumentChecks.ensureNonNull("value", value);
         if (!types.characterizedBy[index].equals(value.getType())) {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedPropertyType_1,
key));
         }
@@ -188,89 +199,104 @@ final class CharacteristicMap extends Ab
     }
 
     /**
-     * Returns the set of entries in this map.
+     * If no characteristic exists for the given name and that name is valid,
+     * creates a new map entry with a default {@code Attribute} characteristic.
+     *
+     * @param  name The name of the characteristic to create, if it does not already exist.
+     * @return {@code true} if a new characteristic has been created for the given name.
+     * @throws IllegalArgumentException if the given key is not the name of a characteristic
in this map.
      */
     @Override
-    public Set<Map.Entry<String, Attribute<?>>> entrySet() {
-        return (characterizedBy != null) ? new Entries() : Collections.emptySet();
+    protected boolean addKey(final String name) {
+        final int index = indexOf(name);
+        if (characterizedBy == null) {
+            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+        }
+        if (characterizedBy[index] == null) {
+            characterizedBy[index] = AbstractAttribute.create(types.characterizedBy[index]);
+            return true;
+        }
+        return false;
     }
 
     /**
-     * The set of entries in the {@link CharacteristicMap}.
+     * Adds the given characteristic if none is currently associated for the same characteristic
name.
+     *
+     * @param  value The characteristic to add.
+     * @return {@code true} if the characteristic has been added.
+     * @throws IllegalArgumentException if given characteristic is not valid for this map.
+     * @throws IllegalStateException if another characteristic already exists for the characteristic
name.
      */
-    private final class Entries extends AbstractSet<Map.Entry<String,Attribute<?>>>
{
-        /** Creates a new set of entries. */
-        Entries() {
+    @Override
+    protected boolean addValue(final Attribute<?> value) {
+        ArgumentChecks.ensureNonNull("value", value);
+        final String key = value.getName().toString();
+        final int index = indexOf(key);
+        if (!types.characterizedBy[index].equals(value.getType())) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedPropertyType_1,
key));
         }
-
-        /** Returns the number of entries. */
-        @Override
-        public int size() {
-            return CharacteristicMap.this.size();
+        if (characterizedBy == null) {
+            characterizedBy = new Attribute<?>[types.characterizedBy.length];
         }
-
-        /** Returns an iterator over the entries. */
-        @Override
-        public Iterator<Map.Entry<String, Attribute<?>>> iterator() {
-            return new Iter();
+        final Attribute<?> previous = characterizedBy[index];
+        if (previous == null) {
+            characterizedBy[index] = value;
+            return true;
+        } else if (previous.equals(value)) {
+            return false;
+        } else {
+            throw new IllegalStateException(Errors.format(Errors.Keys.PropertyAlreadyExists_2,
source.getName(), key));
         }
     }
 
     /**
-     * Iterator over the {@link CharacteristicMap} entries.
+     * Returns an iterator over the entries.
      */
-    private final class Iter implements Iterator<Map.Entry<String, Attribute<?>>>
{
-        /** Index of the next element to return in the iteration. */
-        private int index;
-
-        /** The next element to return, or {@code null} if we reached the end of iteration.
*/
-        private Attribute<?> next;
-
-        /** Index of the element returned by the last call to {@link #next()}, or -1 if none.
*/
-        private int previous;
-
-        /** Creates a new iterator. */
-        Iter() {
-            move();
-        }
-
-        /** Advances {@link #index} to the next attribute to return. */
-        private void move() {
-            previous = -1;
-            while (index < characterizedBy.length) {
-                next = characterizedBy[index];
-                if (next != null) return;
-                index++;
+    @Override
+    protected Iterator<Map.Entry<String, Attribute<?>>> entryIterator()
{
+        return new Iterator<Map.Entry<String, Attribute<?>>>() {
+            /** Index of the next element to return in the iteration. */
+            private int index;
+
+            /** The next element to return, or {@code null} if we reached the end of iteration.
*/
+            private Attribute<?> next;
+
+            /** Index of the element returned by the last call to {@link #next()}, or -1
if none. */
+            private int previous = -1;
+
+            /** Returns {@code true} if there is more entries in the iteration. */
+            @Override
+            public boolean hasNext() {
+                while (index < characterizedBy.length) {
+                    next = characterizedBy[index];
+                    if (next != null) return true;
+                    index++;
+                }
+                next = null;
+                return false;
             }
-            next = null;
-        }
 
-        /** Returns {@code true} if there is more entries in the iteration. */
-        @Override
-        public boolean hasNext() {
-            return next != null;
-        }
-
-        /** Creates and return the next entry. */
-        @Override
-        public Map.Entry<String, Attribute<?>> next() {
-            if (hasNext()) {
-                return new Entry(previous = index++, next);
-            } else {
-                throw new NoSuchElementException();
+            /** Creates and return the next entry. */
+            @Override
+            public Map.Entry<String, Attribute<?>> next() {
+                if (hasNext()) {
+                    return new Entry(previous = index++, next);
+                } else {
+                    throw new NoSuchElementException();
+                }
             }
-        }
 
-        /** Removes the last element returned by {@link #next()}. */
-        @Override
-        public void remove() {
-            if (previous >= 0) {
-                characterizedBy[previous] = null;
-                previous = -1;
-            } else {
-                throw new IllegalStateException();
+            /** Removes the last element returned by {@link #next()}. */
+            @Override
+            public void remove() {
+                if (previous >= 0) {
+                    characterizedBy[previous] = null;
+                    previous = -1;
+                } else {
+                    throw new IllegalStateException();
+                }
             }
-        }
+        };
     }
 
     /**
@@ -294,7 +320,7 @@ final class CharacteristicMap extends Ab
         /** Returns the name of the attribute characteristic. */
         @Override
         public String getKey() {
-            return types.names[index];
+            return value.getType().getName().toString();
         }
 
         /** Returns the attribute characteristic (never {@code null}). */

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java?rev=1639971&r1=1639970&r2=1639971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
[UTF-8] Sun Nov 16 06:40:24 2014
@@ -17,14 +17,14 @@
 package org.apache.sis.feature;
 
 import java.util.Map;
-import java.util.Set;
 import java.util.HashMap;
-import java.util.AbstractMap;
-import java.util.AbstractSet;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import org.apache.sis.internal.util.AbstractMap;
+import org.apache.sis.internal.util.AbstractMapEntry;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.util.collection.Containers;
+import org.apache.sis.util.collection.WeakValueHashMap;
 import org.apache.sis.util.resources.Errors;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
@@ -53,16 +53,24 @@ import org.opengis.feature.AttributeType
  */
 final class CharacteristicTypeMap extends AbstractMap<String,AttributeType<?>>
{
     /**
-     * Characteristics of an other attribute type (the {@code source} attribute given to
the constructor).
-     * This array shall not be modified.
+     * For sharing the same {@code CharacteristicTypeMap} instances among the attribute types
+     * having the same characteristics.
+     */
+    @SuppressWarnings("unchecked")
+    private static final WeakValueHashMap<AttributeType<?>[],CharacteristicTypeMap>
SHARED =
+            new WeakValueHashMap<>((Class) AttributeType[].class);
+
+    /*
+     * This class has intentionally no reference to the AttributeType for which we are providing
characteristics.
+     * This allows us to use the same CharacteristicTypeMap instance for various attribute
types having the same
+     * characteristic (e.g. many measurements may have an "accuracy" characteristic).
      */
-    final AttributeType<?>[] characterizedBy;
 
     /**
-     * Name of the {@code characterizedBy} attribute types, used only during iteration over
map entries.
+     * Characteristics of an other attribute type (the {@code source} attribute given to
the constructor).
      * This array shall not be modified.
      */
-    final String[] names;
+    final AttributeType<?>[] characterizedBy;
 
     /**
      * The names of attribute types listed in the {@link #characterizedBy} array,
@@ -71,22 +79,46 @@ final class CharacteristicTypeMap extend
     final Map<String,Integer> indices;
 
     /**
+     * Creates a new map or return an existing map for the given attribute characteristics.
+     *
+     * <p>This method does not clone the {@code characterizedBy} array. If that array
+     * is a user-provided argument, then cloning that array is caller responsibility.</p>
+     *
+     * @param  source The attribute which is characterized by {@code characterizedBy}.
+     * @param  characterizedBy Characteristics of {@code source}. Should not be empty.
+     * @return A map for this given characteristics.
+     * @throws IllegalArgumentException if two characteristics have the same name.
+     */
+    static CharacteristicTypeMap create(final AttributeType<?> source, final AttributeType<?>[]
characterizedBy) {
+        CharacteristicTypeMap map;
+        synchronized (SHARED) {
+            map = SHARED.get(characterizedBy);
+            if (map == null) {
+                map = new CharacteristicTypeMap(source, characterizedBy);
+                SHARED.put(characterizedBy, map);
+            }
+        }
+        return map;
+    }
+
+    /**
      * Creates a new map for the given attribute characteristics.
      *
+     * <p>This constructor does not clone the {@code characterizedBy} array. If that
array
+     * is a user-provided argument, then cloning that array is caller responsibility.</p>
+     *
      * @param  source The attribute which is characterized by {@code characterizedBy}.
      * @param  characterizedBy Characteristics of {@code source}. Should not be empty.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    CharacteristicTypeMap(final AttributeType<?> source, final AttributeType<?>[]
characterizedBy) {
-        this.characterizedBy = characterizedBy.clone();
-        names = new String[characterizedBy.length];
+    private CharacteristicTypeMap(final AttributeType<?> source, final AttributeType<?>[]
characterizedBy) {
+        this.characterizedBy = characterizedBy;
         int index = 0;
         final Map<String,Integer> indices = new HashMap<>(Containers.hashMapCapacity(characterizedBy.length));
         for (int i=0; i<characterizedBy.length; i++) {
             final AttributeType<?> attribute = characterizedBy[i];
             ensureNonNullElement("characterizedBy", i, attribute);
             final String name = AbstractIdentifiedType.toString(attribute.getName(), source,
"characterizedBy", i);
-            names[index] = name;
             if (indices.put(name, index++) != null) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.DuplicatedIdentifier_1,
name));
             }
@@ -133,59 +165,55 @@ final class CharacteristicTypeMap extend
     }
 
     /**
-     * Returns the set of entries in this map.
+     * Returns an iterator over the entries.
+     * This is not the iterator returned by public API like {@code Map.entrySet().iterator()}.
      */
     @Override
-    public Set<Entry<String, AttributeType<?>>> entrySet() {
-        return new Entries();
-    }
-
-    /**
-     * The set of entries in the {@link CharacteristicTypeMap}.
-     */
-    private final class Entries extends AbstractSet<Entry<String,AttributeType<?>>>
{
-        /** Creates a new set of entries. */
-        Entries() {
-        }
-
-        /** Returns the number of entries. */
-        @Override
-        public int size() {
-            return CharacteristicTypeMap.this.size();
-        }
-
-        /** Returns an iterator over the entries. */
-        @Override
-        public Iterator<Entry<String, AttributeType<?>>> iterator() {
-            return new Iter();
-        }
+    protected Iterator<Entry<String, AttributeType<?>>> entryIterator()
{
+        return new Iter();
     }
 
     /**
-     * Iterator over the {@link CharacteristicTypeMap} entries.
+     * Iterator over the {@link CharacteristicTypeMap} entries. The entries returned by this
method are always
+     * {@code this} (in order to avoid temporary objects creation), which is sufficient for
{@link AbstractMap}
+     * needs. This is not the iterator returned by public API like {@code Map.entrySet().iterator()}.
      */
-    private final class Iter implements Iterator<Entry<String, AttributeType<?>>>
{
+    private final class Iter extends AbstractMapEntry<String, AttributeType<?>>
+            implements Iterator<Entry<String, AttributeType<?>>>
+    {
         /** Index of the next element to return in the iteration. */
         private int index;
 
+        /** Value of current entry. */
+        private AttributeType<?> value;
+
         /** Creates a new iterator. */
         Iter() {
         }
 
         /** Returns {@code true} if there is more entries in the iteration. */
-        @Override
-        public boolean hasNext() {
+        @Override public boolean hasNext() {
             return index < characterizedBy.length;
         }
 
         /** Creates and return the next entry. */
-        @Override
-        public Entry<String, AttributeType<?>> next() {
+        @Override public Entry<String, AttributeType<?>> next() {
             if (hasNext()) {
-                return new SimpleImmutableEntry<>(names[index], characterizedBy[index++]);
+                value = characterizedBy[index++];
+                return this;
             } else {
                 throw new NoSuchElementException();
             }
         }
+
+        /** Returns the attribute characteristic name. */
+        @Override public String getKey() {
+            return value.getName().toString();
+        }
+
+        /** Returns the attribute characteristic contained in this entry. */
+        @Override public AttributeType<?> getValue() {
+            return value;
+        }
     }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java?rev=1639971&r1=1639970&r2=1639971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
[UTF-8] Sun Nov 16 06:40:24 2014
@@ -183,7 +183,7 @@ public class DefaultAttributeType<V> ext
         this.valueClass      = valueClass;
         this.defaultValue    = Numerics.cached(defaultValue);
         if (characterizedBy != null && characterizedBy.length != 0) {
-            characteristics = new CharacteristicTypeMap(this, characterizedBy);
+            characteristics = CharacteristicTypeMap.create(this, characterizedBy.clone());
         }
     }
 
@@ -210,7 +210,7 @@ public class DefaultAttributeType<V> ext
         try {
             final AttributeType<?>[] characterizedBy = (AttributeType<?>[]) in.readObject();
             if (characterizedBy != null) {
-                characteristics = new CharacteristicTypeMap(this, characterizedBy);
+                characteristics = CharacteristicTypeMap.create(this, characterizedBy);
             }
         } catch (RuntimeException e) { // At least ClassCastException, NullPointerException
and IllegalArgumentException.
             throw (IOException) new InvalidObjectException(e.getMessage()).initCause(e);
@@ -291,6 +291,8 @@ public class DefaultAttributeType<V> ext
      * </div>
      *
      * @return Other attribute types that describes this attribute type, or an empty set
if none.
+     *
+     * @see AbstractAttribute#characteristics()
      */
     public Map<String,AttributeType<?>> characteristics() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();

Added: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java?rev=1639971&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
(added)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
[UTF-8] Sun Nov 16 06:40:24 2014
@@ -0,0 +1,101 @@
+/*
+ * 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.feature;
+
+import java.util.Map;
+import java.util.AbstractMap.SimpleEntry;
+import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static java.util.Collections.singletonMap;
+import static org.apache.sis.feature.DefaultAssociationRole.NAME_KEY;
+import static org.apache.sis.test.Assert.*;
+
+// Branch-dependent imports
+import org.opengis.feature.AttributeType;
+
+
+/**
+ * Tests {@link CharacteristicTypeMap} indirectly, through {@link DefaultAttributeType} construction.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.5
+ * @module
+ */
+@DependsOn(DefaultAttributeTypeTest.class)
+public final strictfp class CharacteristicTypeMapTest extends TestCase {
+    /**
+     * Creates an attribute type for a temperature measurement in °C with a ±0.1°C accuracy.
+     *
+     * @return A {@code "temperature"} type attribute with two characteristics: {@code "accuracy"}
and {@code "units"}.
+     */
+    public static DefaultAttributeType<Float> temperature() {
+        final DefaultAttributeType<?> units, accuracy;
+        units    = new DefaultAttributeType<>(singletonMap(NAME_KEY, "units"),    
 String.class, 1, 1, "°C", (DefaultAttributeType[]) null);
+        accuracy = new DefaultAttributeType<>(singletonMap(NAME_KEY, "accuracy"), 
  Float.class, 1, 1, 0.1f, (DefaultAttributeType[]) null);
+        return     new DefaultAttributeType<>(singletonMap(NAME_KEY, "temperature"),
Float.class, 1, 1, null, accuracy, units);
+    }
+
+    /**
+     * Tests the creation of two attributes with the same characteristics.
+     * We expect those attributes to share the same {@link CharacteristicTypeMap} instance.
+     * We opportunistically test {@code equals} and {@code hashCode} first, because sharing
+     * is unlikely to work if those methods did not worked properly.
+     */
+    @Test
+    public void testTwoAttributes() {
+        final DefaultAttributeType<Float> t1 = temperature();
+        final DefaultAttributeType<Float> t2 = temperature();
+        assertNotSame(t1, t2); // Remaining of this test is useless if instances are the
same.
+        assertTrue  ("equals",   t1.equals(t2));
+        assertTrue  ("equals",   t2.equals(t1));
+        assertEquals("hashCode", t1.hashCode(), t2.hashCode());
+        assertSame  ("shared",   t1.characteristics(), t2.characteristics());
+    }
+
+    /**
+     * Tests various methods from the {@link Map} interface.
+     */
+    @Test
+    public void testMapMethods() {
+        final AttributeType<?> units, accuracy;
+        final DefaultAttributeType<Float> temperature = temperature();
+        final Map<String, AttributeType<?>> characteristics = temperature.characteristics();
+
+        assertFalse  ("isEmpty",        characteristics.isEmpty());
+        assertEquals ("size", 2,        characteristics.size());
+        assertTrue   ("containsKey",    characteristics.containsKey("units"));
+        assertTrue   ("containsKey",    characteristics.containsKey("accuracy"));
+        assertFalse  ("containsKey",    characteristics.containsKey("temperature"));
+        assertNotNull("get", units    = characteristics.get("units"));
+        assertNotNull("get", accuracy = characteristics.get("accuracy"));
+        assertNull   ("get",            characteristics.get("temperature"));
+        assertSame   ("get", units,     characteristics.get("units"));
+        assertSame   ("get", accuracy,  characteristics.get("accuracy"));
+        assertTrue   ("containsValue",  characteristics.containsValue(units));
+        assertTrue   ("containsValue",  characteristics.containsValue(accuracy));
+        assertFalse  ("containsValue",  characteristics.containsValue(temperature));
+        assertArrayEquals("keySet", new String[] {"accuracy", "units"}, characteristics.keySet().toArray());
+        assertArrayEquals("values", new Object[] { accuracy ,  units }, characteristics.values().toArray());
+        assertArrayEquals("entrySet", new Object[] {
+                new SimpleEntry<>("accuracy", accuracy),
+                new SimpleEntry<>("units",    units)
+            }, characteristics.entrySet().toArray());
+    }
+}

Propchange: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java?rev=1639971&r1=1639970&r2=1639971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java
[UTF-8] Sun Nov 16 06:40:24 2014
@@ -32,6 +32,8 @@ import static org.apache.sis.test.Assert
 
 /**
  * Tests {@link DefaultAttributeType}.
+ * This class does not test {@link DefaultAttributeType#characteristics()}.
+ * Characteristics are tested by {@link CharacteristicTypeMapTest} instead.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5

Modified: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java?rev=1639971&r1=1639970&r2=1639971&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
[UTF-8] Sun Nov 16 06:40:24 2014
@@ -31,6 +31,7 @@ import org.junit.BeforeClass;
  */
 @Suite.SuiteClasses({
     org.apache.sis.feature.DefaultAttributeTypeTest.class,
+    org.apache.sis.feature.CharacteristicTypeMapTest.class,
     org.apache.sis.feature.DefaultFeatureTypeTest.class,
     org.apache.sis.feature.PropertySingletonTest.class,
     org.apache.sis.feature.SingletonAttributeTest.class,



Mime
View raw message