sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1784397 - in /sis/branches/JDK8/core/sis-referencing-by-identifiers/src: main/java/org/apache/sis/referencing/gazetteer/ test/java/org/apache/sis/referencing/gazetteer/ test/java/org/apache/sis/test/suite/
Date Sat, 25 Feb 2017 16:38:49 GMT
Author: desruisseaux
Date: Sat Feb 25 16:38:49 2017
New Revision: 1784397

URL: http://svn.apache.org/viewvc?rev=1784397&view=rev
Log:
Rename LocationTypeTemplate as ModifiableLocationType, implement toString(), equals(Object) and hashCode(), add tests and documentation.

Added:
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java   (with props)
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java
      - copied, changed from r1784396, sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeSnapshot.java
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
      - copied, changed from r1784396, sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeTemplate.java
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java   (with props)
Removed:
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeSnapshot.java
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeTemplate.java
Modified:
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/test/suite/ReferencingByIdentifiersTestSuite.java

Added: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java?rev=1784397&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java (added)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java [UTF-8] Sat Feb 25 16:38:49 2017
@@ -0,0 +1,187 @@
+/*
+ * 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.referencing.gazetteer;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.IdentityHashMap;
+import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import org.opengis.referencing.gazetteer.LocationType;
+import org.apache.sis.util.collection.DefaultTreeTable;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.LenientComparable;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Utilities;
+import org.apache.sis.util.Debug;
+
+
+/**
+ * Default implementation of {@code toString()}, {@code equals(Object)} and {@code hashCode()} methods
+ * for {@code LocationType} implementations.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+abstract class AbstractLocationType implements LocationType, LenientComparable {
+    /**
+     * For sub-class constructors.
+     */
+    protected AbstractLocationType() {
+    }
+
+    /**
+     * Creates unmodifiable snapshots of the given location types. This method returns a new collection within which
+     * all elements are snapshots of the given location types (in iteration order), except the reference system which
+     * is set to the given value.
+     *
+     * <p>The location types returned by this method are {@linkplain java.io.Serializable serializable}
+     * if all properties ({@linkplain ModifiableLocationType#getName() name},
+     * {@linkplain ModifiableLocationType#getTerritoryOfUse() territory of use}, <i>etc.</i>
+     * are also serializable).</p>
+     *
+     * @param  rs     the reference system to assign to the new location types, or {@code null} if none.
+     * @param  types  the location types for which to take a snapshot.
+     * @return unmodifiable copies of the given location types.
+     */
+    public static List<LocationType> snapshot(final ReferenceSystemUsingIdentifiers rs, final LocationType... types) {
+        ArgumentChecks.ensureNonNull("types", types);
+        return FinalLocationType.snapshot(Arrays.asList(types), rs, new IdentityHashMap<>());
+    }
+
+    /**
+     * Compares this location type with the specified object for equality.
+     * This method compares the value of {@link #getName()} and {@link #getChildren()} in all modes.
+     * At the opposite, values of {@link #getParents()} and {@link #getReferenceSystem()} are never
+     * compared, no matter the mode, for avoiding never-ending loops. Other properties may or may
+     * not be compared depending on the {@code mode} argument.
+     *
+     * <p>If the {@code mode} argument value is {@link ComparisonMode#STRICT STRICT} or
+     * {@link ComparisonMode#BY_CONTRACT BY_CONTRACT}, then almost all properties are compared
+     * including the {@linkplain #getTheme() theme} and the {@linkplain #getOwner() owner}.</p>
+     *
+     * @param  object  the object to compare to {@code this}.
+     * @param  mode    {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or
+     *                 {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for comparing only
+     *                 properties relevant to location identifications.
+     * @return {@code true} if both objects are equal.
+     */
+    @Override
+    @SuppressWarnings("fallthrough")
+    public boolean equals(final Object object, final ComparisonMode mode) {
+        if (object == this) {
+            return true;
+        }
+        if (object != null) switch (mode) {
+            case STRICT: {
+                if (getClass() != object.getClass()) break;
+                // Fall through
+            }
+            case BY_CONTRACT: {
+                if (!(object instanceof LocationType)) break;
+                final LocationType that = (LocationType) object;
+                // Do not compare the ReferenceSystem as it may cause an infinite recursion.
+                if (!Utilities.deepEquals(getTheme(),           that.getTheme(),           mode) ||
+                    !Utilities.deepEquals(getIdentifications(), that.getIdentifications(), mode) ||
+                    !Utilities.deepEquals(getDefinition(),      that.getDefinition(),      mode) ||
+                    !Utilities.deepEquals(getTerritoryOfUse(),  that.getTerritoryOfUse(),  mode) ||
+                    !Utilities.deepEquals(getOwner(),           that.getOwner(),           mode))
+                {
+                    break;
+                }
+                // Fall through
+            }
+            default: {
+                if (!(object instanceof LocationType)) break;
+                final LocationType that = (LocationType) object;
+                if (Objects.equals(getName(), that.getName())) {
+                    /*
+                     * To be safe, we should apply some check against infinite recursivity here.
+                     * We do not on the assumption that the constructor verified that we do not
+                     * have any cycle.
+                     */
+                    return Utilities.deepEquals(getChildren(), that.getChildren(), mode);
+                }
+                break;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Compares this location type with the specified object for strict equality.
+     * This method compares all properties except the value returned by {@link #getParents()}
+     * and {@link #getReferenceSystem()}, for avoiding never-ending loops.
+     *
+     * <p>This method is implemented as below:</p>
+     * {@preformat java
+     *     return equals(object, ComparisonMode.STRICT);
+     * }
+     *
+     * @param  object  the object to compare to {@code this}.
+     * @return {@code true} if both objects are equal.
+     */
+    @Override
+    @SuppressWarnings("fallthrough")
+    public final boolean equals(final Object object) {
+        return equals(object, ComparisonMode.STRICT);
+    }
+
+    /**
+     * Returns a hash code value for this location type.
+     *
+     * @return a hash code value for this location type.
+     */
+    @Override
+    public int hashCode() {
+        int code = Objects.hashCode(getName());
+        for (final LocationType child : getChildren()) {
+            // Take only children name without recursivity over their own children.
+            code = code*31 + Objects.hashCode(child.getName());
+        }
+        return code;
+    }
+
+    /**
+     * Returns a string representation of this location type and all its children.
+     * The string representation is mostly for debugging purpose and may change in any future SIS version.
+     *
+     * @return a string representation of this location type.
+     */
+    @Debug
+    @Override
+    public String toString() {
+        final DefaultTreeTable table = new DefaultTreeTable(TableColumn.NAME, TableColumn.VALUE_AS_TEXT);
+        format(this, table.getRoot());
+        return table.toString();
+    }
+
+    /**
+     * Invoked recursively for formatting the given type in the given tree.
+     */
+    private static void format(final LocationType type, final TreeTable.Node node) {
+        node.setValue(TableColumn.NAME, type.getName());
+        node.setValue(TableColumn.VALUE_AS_TEXT, type.getDefinition());
+        for (final LocationType child : type.getChildren()) {
+            format(child, node.newChild());
+        }
+    }
+}

Propchange: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Copied: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java (from r1784396, sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeSnapshot.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java?p2=sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java&p1=sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeSnapshot.java&r1=1784396&r2=1784397&rev=1784397&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeSnapshot.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java [UTF-8] Sat Feb 25 16:38:49 2017
@@ -41,7 +41,7 @@ import org.apache.sis.util.ArgumentCheck
  * @version 0.8
  * @module
  */
-final class LocationTypeSnapshot implements LocationType, Serializable {
+final class FinalLocationType extends AbstractLocationType implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -59,6 +59,7 @@ final class LocationTypeSnapshot impleme
 
     /**
      * Method(s) of uniquely identifying location instances.
+     * This list is unmodifiable.
      */
     private final List<InternationalString> identifications;
 
@@ -84,28 +85,30 @@ final class LocationTypeSnapshot impleme
 
     /**
      * Parent location types (location types of which this location type is a sub-division).
+     * This list is unmodifiable.
      */
     private final List<LocationType> parents;
 
     /**
      * Child location types (location types which sub-divides this location type).
+     * This list is unmodifiable.
      */
     private final List<LocationType> children;
 
     /**
      * Creates a copy of the given location type with the reference system set to the given value.
      *
-     * @param source    the location type to use as a template.
+     * @param source    the location type to copy.
      * @param rs        the reference system that comprises this location type.
-     * @param existing  other {@code LocationTypeSnapshot} instances created before this one.
+     * @param existing  other {@code FinalLocationType} instances created before this one.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
-    private LocationTypeSnapshot(final LocationType source, final ReferenceSystemUsingIdentifiers rs,
-            final Map<LocationType, LocationTypeSnapshot> existing)
+    private FinalLocationType(final LocationType source, final ReferenceSystemUsingIdentifiers rs,
+            final Map<LocationType, FinalLocationType> existing)
     {
         /*
          * Put 'this' in the map at the beginning in case the parents and children contain cyclic references.
-         * Cyclic references are not allowed if the source are LocationTypeTemplate, but the user could have
+         * Cyclic references are not allowed if the source are ModifiableLocationType, but the user could have
          * given its own implementation. Having the 'this' reference escaped in object construction should not
          * be an issue here because this is a private constructor, and we use it in such a way that if an
          * exception is thrown, the whole tree (with all 'this' references) will be discarded.
@@ -123,21 +126,23 @@ final class LocationTypeSnapshot impleme
     }
 
     /**
-     * Copies the content of the given collection.
+     * Creates a snapshot of the given location types. This method returns a new collection within which
+     * all elements are snapshots (as {@code FinalLocationType} instances) of the given location types,
+     * except the reference system which is set to the given value.
      *
      * @param rs        the reference system to assign to the new location types.
      * @param existing  an initially empty identity hash map for internal usage by this method.
      */
     static List<LocationType> snapshot(final Collection<? extends LocationType> types,
-            final ReferenceSystemUsingIdentifiers rs, final Map<LocationType, LocationTypeSnapshot> existing)
+            final ReferenceSystemUsingIdentifiers rs, final Map<LocationType, FinalLocationType> existing)
     {
         final LocationType[] array = types.toArray(new LocationType[types.size()]);
         for (int i=0; i < array.length; i++) {
             final LocationType source = array[i];
             ArgumentChecks.ensureNonNullElement("types", i, source);
-            LocationTypeSnapshot copy = existing.get(source);
+            FinalLocationType copy = existing.get(source);
             if (copy == null) {
-                copy = new LocationTypeSnapshot(source, rs, existing);
+                copy = new FinalLocationType(source, rs, existing);
             }
             array[i] = copy;
         }
@@ -154,7 +159,7 @@ final class LocationTypeSnapshot impleme
     @SuppressWarnings("unchecked")
     private static List<InternationalString> snapshot(final Collection<? extends InternationalString> c) {
         if (c instanceof UnmodifiableArrayList<?>) {
-            return (List<InternationalString>) c;
+            return (List<InternationalString>) c;       // Unsafe cast okay because we allow only read operations.
         } else {
             return UnmodifiableArrayList.wrap(c.toArray(new InternationalString[c.size()]));
         }

Copied: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java (from r1784396, sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeTemplate.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java?p2=sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java&p1=sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeTemplate.java&r1=1784396&r2=1784397&rev=1784397&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationTypeTemplate.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java [UTF-8] Sat Feb 25 16:38:49 2017
@@ -17,29 +17,100 @@
 package org.apache.sis.referencing.gazetteer;
 
 import java.util.Map;
-import java.util.List;
-import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.ConcurrentModificationException;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Party;
 import org.opengis.metadata.extent.GeographicExtent;
-import org.opengis.referencing.gazetteer.LocationType;
 import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicDescription;
+import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
+import org.apache.sis.util.CorruptedObjectException;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.iso.Types;
 
+// Branch-dependent imports
+import java.util.function.Function;
+
 
 /**
- * Description of a location to be used as a template in reference system construction.
- * This object can be used as a {@link LocationType} builder.
+ * Helper class for building the description of a location. Temporary instances of this class can be used
+ * during the construction of <cite>spatial reference systems using geographic identifiers</cite>.
+ * Since {@code ModifiableLocationType} instances are modifiable, they should not be published directly.
+ * Instead, unmodifiable {@linkplain #snapshot snapshots} should be published.
+ * The same {@code ModifiableLocationType} instance can be used for many snapshots.
+ *
+ * <div class="note"><b>Example:</b>
+ * the following code creates 3 levels of location types: <var>administrative areas</var>, which contain
+ * <var>towns</var>, which themselves contain <var>streets</var>. Note that the {@code street} location
+ * type has two parents, {@code town} and {@code area}, because a street can be outside any town and
+ * directly under the authority of an administrative area instead.
+ *
+ * {@preformat java
+ *   ModifiableLocationType area   = new ModifiableLocationType("administrative area");
+ *   ModifiableLocationType town   = new ModifiableLocationType("town");
+ *   ModifiableLocationType street = new ModifiableLocationType("street");
+ *
+ *   area  .setTheme("local administration");
+ *   town  .setTheme("built environment");
+ *   street.setTheme("access");
+ *
+ *   area  .setDefinition("area of responsibility of highest level local authority");
+ *   town  .setDefinition("city or town");
+ *   street.setDefinition("thoroughfare providing access to properties");
+ *
+ *   town  .addParent(area);
+ *   street.addParent(area);
+ *   street.addParent(town);
+ * }
+ *
+ * A string representation of the {@code area} location type is as below:
+ *
+ * {@preformat text
+ *   administrative area………………… area of responsibility of highest level local authority
+ *     ├─town……………………………………………… city or town
+ *     │   └─street……………………………… thoroughfare providing access to properties
+ *     └─street………………………………………… thoroughfare providing access to properties
+ * }
+ * </div>
+ *
+ * <div class="section">Inheritance of property values</div>
+ * According ISO 19112:2003, all properties except the collection of
+ * {@linkplain #getParents() parents} and {@linkplain #getChildren() children} are mandatory.
+ * Those mandatory properties are the {@linkplain #getName() name}, {@linkplain #getTheme() theme},
+ * {@linkplain #getIdentifications() identifications}, {@linkplain #getDefinition() definition},
+ * {@linkplain #getTerritoryOfUse() territory of use} and {@linkplain #getOwner() owner}.
+ * However in Apache SIS implementation, only the name is truly mandatory;
+ * SIS is tolerant to missing value for all other properties.
+ * But in the hope to improve ISO compliance, values of undefined properties are inherited
+ * from the parents (if any) provided that all parents define the same values.
+ *
+ * <div class="note"><b>Example:</b>
+ * if an <var>administrative area</var> is in some {@linkplain #getTerritoryOfUse() territory of use},
+ * then all children of the administrative area (namely <var>towns</var> and <var>streets</var>) can
+ * reasonably presumed to be in the same territory of use. That territory can be specified only once
+ * as below:
+ *
+ * {@preformat java
+ *   area.setTerritoryOfUse("Japan");
+ * }
+ *
+ * Then, the towns and streets automatically inherit the same value for that property,
+ * unless they are explicitely given another value.</div>
+ *
+ * <div class="section">Limitation</div>
+ * This class is not serializable and is not thread-safe. For thread safety or for serialization,
+ * a {@linkplain #snapshot snapshots} of this location type should be taken.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8
  * @version 0.8
  * @module
  */
-public class LocationTypeTemplate implements LocationType {
+public class ModifiableLocationType extends AbstractLocationType {      // Not Serializable on intend.
     /**
      * Name of the location type.
      */
@@ -53,7 +124,7 @@ public class LocationTypeTemplate implem
     /**
      * Method(s) of uniquely identifying location instances.
      */
-    private final List<InternationalString> identifications;
+    private final Map<String, InternationalString> identifications;
 
     /**
      * The way in which location instances are defined.
@@ -73,22 +144,22 @@ public class LocationTypeTemplate implem
     /**
      * Parent location types (location types of which this location type is a sub-division).
      */
-    private final Map<String, LocationTypeTemplate> parents;
+    private final Map<String, ModifiableLocationType> parents;
 
     /**
      * Child location types (location types which sub-divides this location type).
      */
-    private final Map<String, LocationTypeTemplate> children;
+    private final Map<String, ModifiableLocationType> children;
 
     /**
      * Creates a new location type of the given name.
      *
      * @param name the location type name.
      */
-    public LocationTypeTemplate(final CharSequence name) {
+    public ModifiableLocationType(final CharSequence name) {
         ArgumentChecks.ensureNonNull("name", name);
         this.name       = Types.toInternationalString(name);
-        identifications = new ArrayList<>();
+        identifications = new LinkedHashMap<>();
         parents         = new LinkedHashMap<>();
         children        = new LinkedHashMap<>();
     }
@@ -108,7 +179,39 @@ public class LocationTypeTemplate implem
     }
 
     /**
+     * If all parents return the same value for the given property, returns that value.
+     * Otherwise returns {@code null}.
+     */
+    private <E> E inherit(final Function<ModifiableLocationType, E> property) {
+        E common = null;
+        for (final ModifiableLocationType parent : parents.values()) {
+            final E value = property.apply(parent);
+            if (value != null) {
+                if (common == null) {
+                    common = value;
+                } else if (!value.equals(common)) {
+                    return null;
+                }
+            }
+        }
+        return common;
+    }
+
+    /**
      * Returns the property used as the defining characteristic of the location type.
+     * If no theme has been explicitely set, then this method inherits the value from
+     * the parents providing that all parents specify the same theme.
+     *
+     * @return property used as the defining characteristic of the location type,
+     *         or {@code null} if no value has been defined or can be inherited.
+     */
+    @Override
+    public InternationalString getTheme() {
+        return (theme != null) ? theme : inherit(ModifiableLocationType::getTheme);
+    }
+
+    /**
+     * Sets the property used as the defining characteristic of the location type.
      *
      * <div class="note"><b>Examples:</b>
      * <cite>“local administration”</cite> for administrative areas,
@@ -117,49 +220,71 @@ public class LocationTypeTemplate implem
      * <cite>“electoral”</cite>,
      * <cite>“postal”</cite>.</div>
      *
-     * @return property used as the defining characteristic of the location type.
-     *
-     * @see ReferenceSystemUsingIdentifiers#getTheme()
+     * @param  value  the new theme, or {@code null} for inheriting a value from the parents.
      */
-    @Override
-    public InternationalString getTheme() {
-        return theme;
+    public void setTheme(final CharSequence value) {
+        theme = Types.toInternationalString(value);
     }
 
     /**
-     * Sets the property used as the defining characteristic of the location type.
+     * Returns the method(s) of uniquely identifying location instances.
+     * If no methods have been explicitely set, then this method inherits the values from
+     * the parents providing that all parents specify the same methods.
+     *
+     * <p>The collection returned by this method is unmodifiable. For adding or removing an identification,
+     * use {@link #addIdentification(CharSequence)} or {@link #removeIdentification(CharSequence)}.</p>
      *
-     * @param  value  the new theme.
+     * @return method(s) of uniquely identifying location instances,
+     *         or an empty list if no value has been defined or can be inherited.
      */
-    public void setTheme(final CharSequence value) {
-        theme = Types.toInternationalString(value);
+    @Override
+    public Collection<InternationalString> getIdentifications() {
+        return identifications.isEmpty() ? inherit(ModifiableLocationType::getIdentifications)
+                : Collections.unmodifiableCollection(identifications.values());
     }
 
     /**
-     * Returns the method(s) of uniquely identifying location instances.
+     * Adds a method of uniquely identifying location instances.
      *
      * <div class="note"><b>Examples:</b>
      * “name”, “code”, “unique street reference number”, “geographic address”.</div>
      *
-     * The list returned by this method is <cite>live</cite>; changes in that list
-     * will be reflected immediately in this {@code LocationTypeTemplate} and conversely.
+     * @param  value  the method to add.
+     * @throws IllegalArgumentException if the given value is already defined.
+     */
+    public void addIdentification(final CharSequence value) {
+        ArgumentChecks.ensureNonNull("value", value);
+        final String key = value.toString();
+        if (identifications.putIfAbsent(key, Types.toInternationalString(value)) != null) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementAlreadyPresent_1, key));
+        }
+    }
+
+    /**
+     * Removes a method of uniquely identifying location instances.
      *
-     * @return method(s) of uniquely identifying location instances.
+     * @param  value  the method to remove.
+     * @throws IllegalArgumentException if the given value is not found.
      */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public List<InternationalString> getIdentifications() {
-        return identifications;
+    public void removeIdentification(final CharSequence value) {
+        ArgumentChecks.ensureNonNull("value", value);
+        final String key = value.toString();
+        if (identifications.remove(key) == null) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementNotFound_1, key));
+        }
     }
 
     /**
      * Returns the way in which location instances are defined.
+     * If no definition has been explicitely set, then this method inherits the value from
+     * the parents providing that all parents specify the same definition.
      *
-     * @return the way in which location instances are defined.
+     * @return the way in which location instances are defined,
+     *         or {@code null} if no value has been defined or can be inherited.
      */
     @Override
     public InternationalString getDefinition() {
-        return definition;
+        return (definition != null) ? definition : inherit(ModifiableLocationType::getDefinition);
     }
 
     /**
@@ -167,25 +292,27 @@ public class LocationTypeTemplate implem
      *
      * @param  value  the new identification.
      */
-    public void setDefinition(final InternationalString value) {
-        definition = value;
+    public void setDefinition(final CharSequence value) {
+        definition = Types.toInternationalString(value);
     }
 
     /**
      * Returns the geographic area within which the location type occurs.
+     * If no geographic area has been explicitely set, then this method inherits the value from
+     * the parents providing that all parents specify the same geographic area.
      *
-     * <div class="note"><b>Examples:</b>
-     * the geographic domain for a location type “rivers” might be “North America”.</div>
-     *
-     * @return geographic area within which the location type occurs.
+     * @return geographic area within which the location type occurs,
+     *         or {@code null} if no value has been defined or can be inherited.
      */
     @Override
     public GeographicExtent getTerritoryOfUse() {
-        return territoryOfUse;
+        return (territoryOfUse != null) ? territoryOfUse : inherit(ModifiableLocationType::getTerritoryOfUse);
     }
 
     /**
-     * Sets the geographic area within which the location type occurs.
+     * Sets the geographic area within which the location type occurs. The given value is typically an instance of
+     * {@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox}. For an alternative where only the
+     * territory name is specified, see {@link #setTerritoryOfUse(String)}.
      *
      * @param  value  the new geographic extent.
      */
@@ -194,29 +321,34 @@ public class LocationTypeTemplate implem
     }
 
     /**
-     * Returns the reference system that comprises this location type.
-     * For a {@code LocationTypeTemplate}, the reference system is always null.
-     * The reference system is set when the template is given to {@link ReferencingByIdentifiers} constructor.
+     * Sets the name of the geographic area within which the location type occurs.
      *
-     * @return {@code null}.
+     * <div class="note"><b>Examples:</b>
+     * the geographic domain for a location type “rivers” might be “North America”.</div>
+     *
+     * @param  identifier  the identifier of the geographic extent.
      */
-    @Override
-    public ReferenceSystemUsingIdentifiers getReferenceSystem() {
-        return null;
+    public void setTerritoryOfUse(final String identifier) {
+        territoryOfUse = (identifier != null) ? new DefaultGeographicDescription(null, identifier) : null;
     }
 
     /**
      * Returns the name of organization or class of organization able to create and destroy location instances.
+     * If no organization has been explicitely set, then this method inherits the value from
+     * the parents providing that all parents specify the same organization.
      *
-     * @return organization or class of organization able to create and destroy location instances.
+     * @return organization or class of organization able to create and destroy location instances,
+     *         or {@code null} if no value has been defined or can be inherited.
      */
     @Override
     public Party getOwner() {
-        return owner;
+        return (owner != null) ? owner : inherit(ModifiableLocationType::getOwner);
     }
 
     /**
      * Sets the organization or class of organization able to create and destroy location instances.
+     * The given value is typically an instance of {@link DefaultOrganisation}.
+     * For an alternative where only the organization name is specified, see {@link #setOwner(CharSequence)}.
      *
      * @param  value  the new owner.
      */
@@ -225,26 +357,90 @@ public class LocationTypeTemplate implem
     }
 
     /**
+     * Sets the name of the organization or class of organization able to create and destroy location instances.
+     *
+     * @param  name  the organization name.
+     */
+    public void setOwner(final CharSequence name) {
+        owner = (name != null) ? new DefaultOrganisation(name, null, null, null) : null;
+    }
+
+    /**
      * Returns the parent location types (location types of which this location type is a sub-division).
      * A location type can have more than one possible parent. For example the parent of a location type named
      * <cite>“street”</cite> could be <cite>“locality”</cite>, <cite>“town”</cite> or <cite>“administrative area”</cite>.
      *
+     * <p>The collection returned by this method is unmodifiable. For adding or removing a parent,
+     * use {@link #addParent(ModifiableLocationType)} or {@link #removeParent(ModifiableLocationType)}.</p>
+     *
      * @return parent location types, or an empty collection if none.
      */
     @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Collection<LocationTypeTemplate> getParents() {
-        return parents.values();
+    public final Collection<ModifiableLocationType> getParents() {
+        return Collections.unmodifiableCollection(parents.values());
     }
 
     /**
      * Returns the child location types (location types which sub-divides this location type).
+     * The collection returned by this method is unmodifiable. For adding or removing a child,
+     * use <code>child.{@linkplain #addParent addParent}(this)</code>
+     * or  <code>child.{@linkplain #removeParent removeParent}(this)</code>.
      *
      * @return child location types, or an empty collection if none.
      */
     @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Collection<LocationTypeTemplate> getChildren() {
-        return children.values();
+    public final Collection<ModifiableLocationType> getChildren() {
+        return Collections.unmodifiableCollection(children.values());
+    }
+
+    /**
+     * Adds the given element to the list of parents.
+     *
+     * @param  parent  the parent to add.
+     * @throws IllegalStateException if this location type already have a parent of the same name.
+     * @throws IllegalArgumentException if the given parent already have a child of the same name than this location type.
+     */
+    public void addParent(final ModifiableLocationType parent) {
+        ArgumentChecks.ensureNonNull("parent", parent);
+        final String key = parent.name.toString();
+        if (parents.putIfAbsent(key, parent) != null) {
+            throw new IllegalStateException(Errors.format(Errors.Keys.ElementAlreadyPresent_1, key));
+        }
+        if (parent.children.putIfAbsent(name.toString(), this) != null) {
+            if (parents.remove(key) != parent) {                            // Paranoiac check.
+                throw new ConcurrentModificationException();
+            }
+            throw new IllegalArgumentException("Child already present");    // TODO: localize
+        }
+    }
+
+    /**
+     * Removes the given element from the list of parent.
+     *
+     * @param  parent  the parent to remove.
+     * @throws IllegalArgumentException if the given parent has not been found.
+     */
+    public void removeParent(final ModifiableLocationType parent) {
+        ArgumentChecks.ensureNonNull("parent", parent);
+        final String key = parent.name.toString();
+        final ModifiableLocationType removed = parents.remove(key);
+        if (removed == null) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementNotFound_1, key));
+        }
+        if (removed.children.remove(name.toString()) != this || removed != parent) {
+            throw new CorruptedObjectException();
+        }
+    }
+
+    /**
+     * Returns the reference system that comprises this location type. For {@code ModifiableLocationType}s,
+     * the reference system is always null. The reference system is defined when the location types are
+     * given to the {@link ReferencingByIdentifiers} constructor for example.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public ReferenceSystemUsingIdentifiers getReferenceSystem() {
+        return null;
     }
 }

Modified: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java?rev=1784397&r1=1784396&r2=1784397&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java [UTF-8] Sat Feb 25 16:38:49 2017
@@ -16,10 +16,8 @@
  */
 package org.apache.sis.referencing.gazetteer;
 
-import java.util.Arrays;
 import java.util.Map;
 import java.util.Collection;
-import java.util.IdentityHashMap;
 import java.util.Objects;
 import javax.xml.bind.annotation.XmlTransient;
 import org.opengis.util.InternationalString;
@@ -29,7 +27,6 @@ import org.opengis.referencing.gazetteer
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.iso.Types;
 
@@ -48,6 +45,8 @@ import org.apache.sis.util.iso.Types;
  * @since   0.8
  * @version 0.8
  * @module
+ *
+ * @see ModifiableLocationType
  */
 @XmlTransient
 public class ReferencingByIdentifiers extends AbstractReferenceSystem implements ReferenceSystemUsingIdentifiers {
@@ -136,6 +135,10 @@ public class ReferencingByIdentifiers ex
      *   </tr>
      * </table>
      *
+     * This constructor copies the given {@link LocationType} instances as per
+     * {@link ModifiableLocationType#snapshot(ReferenceSystemUsingIdentifiers, LocationType...)}.
+     * Changes in the given location types after construction will not affect this {@code ReferencingByIdentifiers}.
+     *
      * @param properties  the properties to be given to the coordinate reference system.
      * @param types       description of location type(s) in the spatial reference system.
      */
@@ -144,13 +147,12 @@ public class ReferencingByIdentifiers ex
         super(properties);
         theme = Types.toInternationalString(properties, THEME_KEY);
         overallOwner = Containers.property(properties, OVERALL_OWNER_KEY, Party.class);
-        ArgumentChecks.ensureNonNull("types", types);
         /*
          * Having the 'this' reference escaped in object construction should not be an issue here because
          * we invoke package-private method in such a way that if an exception is thrown, the whole tree
          * (with all 'this' references) will be discarded.
          */
-        locationTypes = LocationTypeSnapshot.snapshot(Arrays.asList(types), this, new IdentityHashMap<>());
+        locationTypes = AbstractLocationType.snapshot(this, types);
     }
 
     /**

Added: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java?rev=1784397&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java (added)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java [UTF-8] Sat Feb 25 16:38:49 2017
@@ -0,0 +1,225 @@
+/*
+ * 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.referencing.gazetteer;
+
+import java.util.List;
+import org.opengis.referencing.gazetteer.LocationType;
+import org.opengis.metadata.extent.GeographicDescription;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.TestUtilities;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Tests {@link AbstractLocationType}, {@link FinalLocationType} and {@link ModifiableLocationType}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+public final strictfp class LocationTypeTest extends TestCase {
+    /**
+     * Create the example given in annex B of ISO 19112:2003.
+     */
+    private static ModifiableLocationType[] create(final boolean inherit) {
+        /*
+         * From larger area to finer one. Each type is the child of the previous type,
+         * except "street" which will have all the 3 parents.
+         */
+        final ModifiableLocationType area     = new ModifiableLocationType("administrative area");
+        final ModifiableLocationType town     = new ModifiableLocationType("town");
+        final ModifiableLocationType locality = new ModifiableLocationType("locality");
+        final ModifiableLocationType street   = new ModifiableLocationType("street");
+        final ModifiableLocationType property = new ModifiableLocationType("property");
+        /*
+         * Property used as the defining characteristic of the location type.
+         */
+        area    .setTheme("local administration");
+        town    .setTheme("built environment");
+        locality.setTheme("community");
+        street  .setTheme("access");
+        property.setTheme("built environment");
+        /*
+         * The way in which location instances are defined.
+         */
+        area    .setDefinition("area of responsibility of highest level local authority");
+        town    .setDefinition("city or town");
+        locality.setDefinition("neighbourhood, suburb, district, village, or settlement");
+        street  .setDefinition("thoroughfare providing access to properties");
+        property.setDefinition("land use");
+        /*
+         * The method(s) of uniquely identifying location instances.
+         * The first 3 levels use the same method; it is possible to avoid repeating them.
+         */
+        area.addIdentification("name");
+        if (!inherit) {
+            town    .addIdentification("name");
+            locality.addIdentification("name");
+        }
+        street  .addIdentification("unique street reference number");
+        property.addIdentification("geographic address");
+        /*
+         * The name of the geographic area within which the location type occurs.
+         * All levels in this test use the same geographic area, so it is possible to declare it only once.
+         */
+        area.setTerritoryOfUse("UK");
+        if (!inherit) {
+            town    .setTerritoryOfUse("UK");
+            locality.setTerritoryOfUse("UK");
+            street  .setTerritoryOfUse("UK");
+            property.setTerritoryOfUse("UK");
+        }
+        /*
+         * The name of the organization or class of organization able to create and destroy location instances.
+         */
+        area    .setOwner("UK government");
+        town    .setOwner("Ordnance Survey");
+        locality.setOwner("local authority");
+        street  .setOwner("highway Authority");
+        property.setOwner("local authority");
+        /*
+         * Hierarchy. Note that the street has 3 parents.
+         */
+        town    .addParent(area);
+        locality.addParent(town);
+        street  .addParent(locality);
+        street  .addParent(town);
+        street  .addParent(area);
+        property.addParent(street);
+        return new ModifiableLocationType[] {area, town, locality, street, property};
+    }
+
+    /**
+     * Verifies the value of a "administrative area" location type.
+     */
+    private static void verify(final LocationType[] type) {
+        assertEquals(5, type.length);
+        verify(type[0], "administrative area",
+                        "local administration",
+                        "area of responsibility of highest level local authority",
+                        "name",
+                        "UK government");
+        verify(type[1], "town",
+                        "built environment",
+                        "city or town",
+                        "name",
+                        "Ordnance Survey");
+        verify(type[2], "locality",
+                        "community",
+                        "neighbourhood, suburb, district, village, or settlement",
+                        "name",
+                        "local authority");
+        verify(type[3], "street",
+                        "access",
+                        "thoroughfare providing access to properties",
+                        "unique street reference number",
+                        "highway Authority");
+        verify(type[4], "property",
+                        "built environment",
+                        "land use",
+                        "geographic address",
+                        "local authority");
+    }
+
+    /**
+     * Verifies the value of a location type created by or copied from {@link #create(boolean)}.
+     */
+    private static void verify(final LocationType type, final String name, final String theme, final String definition,
+            final String identification, final String owner)
+    {
+        assertEquals("name",           name,           String.valueOf(type.getName()));
+        assertEquals("theme",          theme,          String.valueOf(type.getTheme()));
+        assertEquals("definition",     definition,     String.valueOf(type.getDefinition()));
+        assertEquals("identification", identification, String.valueOf(TestUtilities.getSingleton(type.getIdentifications())));
+        assertEquals("owner",          owner,          String.valueOf(type.getOwner().getName()));
+        assertEquals("territoryOfUse", "UK", ((GeographicDescription) type.getTerritoryOfUse()).getGeographicIdentifier().getCode());
+    }
+
+    /**
+     * Tests the creation of the example given in annex B of ISO 19112:2003.
+     * This method does not use inheritance.
+     */
+    @Test
+    public void testExample() {
+        verify(create(false));
+    }
+
+    /**
+     * Tests the creation of the example given in annex B of ISO 19112:2003,
+     * but without explicit declaration of property that can be inherited from the parent.
+     */
+    @Test
+    @DependsOnMethod("testExample")
+    public void testInheritance() {
+        verify(create(true));
+    }
+
+    /**
+     * Tests the creation of an unmodifiable snapshot.
+     */
+    @Test
+    @DependsOnMethod("testInheritance")
+    public void testSnapshot() {
+        final List<LocationType> snapshot = ModifiableLocationType.snapshot(null, create(true));
+        verify(snapshot.toArray(new LocationType[snapshot.size()]));
+    }
+
+    /**
+     * Tests the string representation of location type.
+     */
+    @Test
+    @DependsOnMethod("testInheritance")
+    public void testToString() {
+        assertMultilinesEquals(
+                "administrative area………………… area of responsibility of highest level local authority\n" +
+                "  ├─town……………………………………………… city or town\n" +
+                "  │   ├─locality………………………… neighbourhood, suburb, district, village, or settlement\n" +
+                "  │   │   └─street…………………… thoroughfare providing access to properties\n" +
+                "  │   │       └─property…… land use\n" +
+                "  │   └─street……………………………… thoroughfare providing access to properties\n" +
+                "  │       └─property……………… land use\n" +
+                "  └─street………………………………………… thoroughfare providing access to properties\n" +
+                "      └─property………………………… land use\n", create(true)[0].toString());
+    }
+
+    /**
+     * Tests the equality and hash code value computation.
+     */
+    @Test
+    @DependsOnMethod("testToString")
+    public void testEquals() {
+        final ModifiableLocationType t1 = create(false)[0];
+        final ModifiableLocationType t2 = create(true )[0];
+        assertEquals("hashCode", t1.hashCode(), t2.hashCode());
+        assertEquals("equals", t1, t2);
+        t2.removeIdentification("name");
+        assertNotEquals("equals", t1, t2);
+    }
+
+    /**
+     * Tests serialization.
+     */
+    @Test
+    @DependsOnMethod("testEquals")
+    public void testSerialization() {
+        assertSerializedEquals(ModifiableLocationType.snapshot(null, create(true)));
+    }
+}

Propchange: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/test/suite/ReferencingByIdentifiersTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/test/suite/ReferencingByIdentifiersTestSuite.java?rev=1784397&r1=1784396&r2=1784397&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/test/suite/ReferencingByIdentifiersTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/test/suite/ReferencingByIdentifiersTestSuite.java [UTF-8] Sat Feb 25 16:38:49 2017
@@ -30,6 +30,7 @@ import org.junit.BeforeClass;
  * @module
  */
 @Suite.SuiteClasses({
+    org.apache.sis.referencing.gazetteer.LocationTypeTest.class,
     org.apache.sis.referencing.gazetteer.MilitaryGridReferenceSystemTest.class
 })
 public final strictfp class ReferencingByIdentifiersTestSuite extends TestSuite {



Mime
View raw message