sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1803070 [7/20] - in /sis/branches/JDK9: ./ application/sis-console/src/main/java/org/apache/sis/console/ core/sis-build-helper/ core/sis-build-helper/src/main/java/org/apache/sis/internal/book/ core/sis-build-helper/src/main/java/org/apach...
Date Wed, 26 Jul 2017 16:14:14 GMT
Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultThematicClassificationCorrectness.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultThematicClassificationCorrectness.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultThematicClassificationCorrectness.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultThematicClassificationCorrectness.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -23,6 +23,10 @@ import org.opengis.metadata.quality.Them
 
 /**
  * Comparison of the classes assigned to features or their attributes to a universe of discourse.
+ * The following property is mandatory in a well-formed metadata according ISO 19115:
+ *
+ * <div class="preformat">{@code DQ_ThematicClassificationCorrectness}
+ * {@code   └─result……………} Value obtained from applying a data quality measure.</div>
  *
  * <p><b>Limitations:</b></p>
  * <ul>

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultTopologicalConsistency.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultTopologicalConsistency.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultTopologicalConsistency.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultTopologicalConsistency.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -23,6 +23,10 @@ import org.opengis.metadata.quality.Topo
 
 /**
  * Correctness of the explicitly encoded topological characteristics of the dataset as described by the scope.
+ * The following property is mandatory in a well-formed metadata according ISO 19115:
+ *
+ * <div class="preformat">{@code DQ_TopologicalConsistency}
+ * {@code   └─result……………} Value obtained from applying a data quality measure.</div>
  *
  * <p><b>Limitations:</b></p>
  * <ul>

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultUsability.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultUsability.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultUsability.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultUsability.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -24,6 +24,10 @@ import org.apache.sis.xml.Namespaces;
 
 /**
  * Degree of adherence of a dataset to a specific set of user requirements.
+ * The following property is mandatory in a well-formed metadata according ISO 19115:
+ *
+ * <div class="preformat">{@code QE_Usability}
+ * {@code   └─result……………} Value obtained from applying a data quality measure.</div>
  *
  * <p><b>Limitations:</b></p>
  * <ul>

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -25,6 +25,7 @@ import org.opengis.metadata.spatial.Dime
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.apache.sis.internal.jaxb.gco.GO_Measure;
 import org.apache.sis.metadata.iso.ISOMetadata;
+import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.measure.ValueRange;
 import org.apache.sis.util.ArgumentChecks;
 
@@ -33,6 +34,11 @@ import static org.apache.sis.internal.me
 
 /**
  * Axis properties.
+ * The following properties are mandatory in a well-formed metadata according ISO 19115:
+ *
+ * <div class="preformat">{@code MD_Dimension}
+ * {@code   ├─dimensionName……} Name of the axis.
+ * {@code   └─dimensionSize……} Number of elements along the axis.</div>
  *
  * <p><b>Limitations:</b></p>
  * <ul>
@@ -52,6 +58,7 @@ import static org.apache.sis.internal.me
  * @module
  */
 @SuppressWarnings("CloneableClassWithoutClone")                 // ModifiableMetadata needs shallow clones.
+@TitleProperty(name = "dimensionName")
 @XmlType(name = "MD_Dimension_Type", propOrder = {
     "dimensionName",
     "dimensionSize",
@@ -237,12 +244,12 @@ public class DefaultDimension extends IS
     }
 
     /**
-     * Returns the enhancement/ modifier of the dimension name.
+     * Returns the enhancement / modifier of the dimension name.
      *
      * <div class="note"><b>Example:</b>
      * dimensionName = "column", dimensionTitle = "longitude"</div>
      *
-     * @return the enhancement/ modifier of the dimension name.
+     * @return the enhancement / modifier of the dimension name.
      *
      * @since 0.5
      */
@@ -253,9 +260,9 @@ public class DefaultDimension extends IS
     }
 
     /**
-     * Sets the enhancement/ modifier of the dimension name.
+     * Sets the enhancement / modifier of the dimension name.
      *
-     * @param  newValue  the new enhancement/ modifier of the dimension name.
+     * @param  newValue  the new enhancement / modifier of the dimension name.
      *
      * @since 0.5
      */

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGCP.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGCP.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGCP.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGCP.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -29,6 +29,12 @@ import org.apache.sis.xml.Namespaces;
 
 /**
  * Information on ground control point.
+ * Ground control points (GCP) are large marked targets on the ground,
+ * not to be confused with <cite>localization grid</cite> points embedded in some file formats like GeoTIFF or NetCDF.
+ * The following property is mandatory in a well-formed metadata according ISO 19115:
+ *
+ * <div class="preformat">{@code MI_GCP}
+ * {@code   └─geographicCoordinates……} Geographic or map position of the control point, in either two or three dimensions.</div>
  *
  * <p><b>Limitations:</b></p>
  * <ul>
@@ -42,7 +48,10 @@ import org.apache.sis.xml.Namespaces;
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.3
- * @since   0.3
+ *
+ * @see DefaultGCPCollection
+ *
+ * @since 0.3
  * @module
  */
 @SuppressWarnings("CloneableClassWithoutClone")                 // ModifiableMetadata needs shallow clones.

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGCPCollection.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGCPCollection.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGCPCollection.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGCPCollection.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -24,11 +24,20 @@ import org.opengis.metadata.spatial.GCP;
 import org.opengis.metadata.spatial.GCPCollection;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.util.InternationalString;
+import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.xml.Namespaces;
 
 
 /**
  * Information about a control point collection.
+ * The following properties are mandatory in a well-formed metadata according ISO 19115:
+ *
+ * <div class="preformat">{@code MI_GCPCollection}
+ * {@code   ├─collectionIdentification………} Identifier of the GCP collection.
+ * {@code   ├─collectionName…………………………………} Name of the GCP collection.
+ * {@code   ├─coordinateReferenceSystem……} Coordinate system in which the ground control points are defined.
+ * {@code   └─gcp………………………………………………………………} Ground control point(s) used in the collection.
+ * {@code       └─geographicCoordinates……} Geographic or map position of the control point, in either two or three dimensions.</div>
  *
  * <p><b>Limitations:</b></p>
  * <ul>
@@ -42,10 +51,14 @@ import org.apache.sis.xml.Namespaces;
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.3
- * @since   0.3
+ *
+ * @see DefaultGCP
+ *
+ * @since 0.3
  * @module
  */
 @SuppressWarnings("CloneableClassWithoutClone")                 // ModifiableMetadata needs shallow clones.
+@TitleProperty(name = "collectionName")
 @XmlType(name = "MI_GCPCollection_Type", propOrder = {
     "collectionIdentification",
     "collectionName",

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeometricObjects.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeometricObjects.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeometricObjects.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeometricObjects.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -29,6 +29,10 @@ import static org.apache.sis.internal.me
 
 /**
  * Number of objects, listed by geometric object type, used in the dataset.
+ * The following property is mandatory in a well-formed metadata according ISO 19115:
+ *
+ * <div class="preformat">{@code MD_GeometricObjects}
+ * {@code   └─geometricObjectType……} Name of point and vector spatial objects used to locate zero-, one-, and two-dimensional spatial locations in the dataset.</div>
  *
  * <p><b>Limitations:</b></p>
  * <ul>

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeorectified.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeorectified.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeorectified.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeorectified.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -37,10 +37,24 @@ import org.apache.sis.xml.Namespaces;
  * Any cell in the grid can be geolocated given its grid coordinate and the grid origin, cell spacing,
  * and orientation indication of whether or not geographic.
  *
- * <div class="section">Relationship between properties</div>
- * Providing the {@linkplain #getCheckPointDescription() check point description} implies that
- * {@linkplain #isCheckPointAvailable() check point availability} is {@code true}. The setter
- * methods will ensure that this condition is not violated.
+ * <p>The following properties are mandatory or conditional (i.e. mandatory under some circumstances)
+ * in a well-formed metadata according ISO 19115:</p>
+ *
+ * <div class="preformat">{@code MD_Georectified}
+ * {@code   ├─numberOfDimensions…………………………………………………} Number of independent spatial-temporal axes.
+ * {@code   ├─axisDimensionProperties……………………………………} Information about spatial-temporal axis properties.
+ * {@code   │   ├─dimensionName……………………………………………………} Name of the axis.
+ * {@code   │   └─dimensionSize……………………………………………………} Number of elements along the axis.
+ * {@code   ├─cellGeometry…………………………………………………………………} Identification of grid data as point or cell.
+ * {@code   ├─transformationParameterAvailability……} Whether parameters for transformation exists.
+ * {@code   ├─checkPointAvailability………………………………………} Whether geographic position points are available to test the accuracy of the georeferenced grid data.
+ * {@code   ├─checkPointDescription…………………………………………} Description of geographic position points used to test the accuracy of the georeferenced grid data.
+ * {@code   ├─cornerPoints…………………………………………………………………} Earth location in the coordinate reference system and the grid coordinate of the cells at opposite ends.
+ * {@code   └─pointInPixel…………………………………………………………………} Point in a pixel corresponding to the Earth location of the pixel.</div>
+ *
+ * Providing the {@linkplain #getCheckPointDescription() check point description} implies
+ * that {@linkplain #isCheckPointAvailable() check point availability} is {@code true}.
+ * The setter methods will ensure that this condition is not violated.
  *
  * <div class="section">Limitations</div>
  * <ul>

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeoreferenceable.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeoreferenceable.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeoreferenceable.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGeoreferenceable.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -34,6 +34,20 @@ import org.apache.sis.xml.Namespaces;
  * Individual cells can be geolocated using geolocation information supplied with the data but cannot be
  * geolocated from the grid properties alone.
  *
+ * <p>The following properties are mandatory in a well-formed metadata according ISO 19115:</p>
+ *
+ * <div class="preformat">{@code MD_Georeferenceable}
+ * {@code   ├─numberOfDimensions…………………………………………………} Number of independent spatial-temporal axes.
+ * {@code   ├─axisDimensionProperties……………………………………} Information about spatial-temporal axis properties.
+ * {@code   │   ├─dimensionName……………………………………………………} Name of the axis.
+ * {@code   │   └─dimensionSize……………………………………………………} Number of elements along the axis.
+ * {@code   ├─cellGeometry…………………………………………………………………} Identification of grid data as point or cell.
+ * {@code   ├─transformationParameterAvailability……} Indication of whether or not parameters for transformation exists.
+ * {@code   ├─controlPointAvailability…………………………………} Indication of whether or not control point(s) exists.
+ * {@code   ├─orientationParameterAvailability……………} Indication of whether or not orientation parameters are available.
+ * {@code   ├─geolocationInformation………………………………………} Information that can be used to geolocate the data.
+ * {@code   └─georeferencedParameters……………………………………} Terms which support grid data georeferencing.</div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultGridSpatialRepresentation.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -33,6 +33,15 @@ import static org.apache.sis.internal.me
 
 /**
  * Method used to represent geographic information in the dataset.
+ * The following properties are mandatory in a well-formed metadata according ISO 19115:
+ *
+ * <div class="preformat">{@code MD_GridSpatialRepresentation}
+ * {@code   ├─numberOfDimensions…………………………………………………} Number of independent spatial-temporal axes.
+ * {@code   ├─axisDimensionProperties……………………………………} Information about spatial-temporal axis properties.
+ * {@code   │   ├─dimensionName……………………………………………………} Name of the axis.
+ * {@code   │   └─dimensionSize……………………………………………………} Number of elements along the axis.
+ * {@code   ├─cellGeometry…………………………………………………………………} Identification of grid data as point or cell.
+ * {@code   └─transformationParameterAvailability……} Indication of whether or not parameters for transformation exists.</div>
  *
  * <p><b>Limitations:</b></p>
  * <ul>

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/CachedStatement.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/CachedStatement.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/CachedStatement.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/CachedStatement.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -119,7 +119,7 @@ final class CachedStatement implements A
             if (!r.next()) {
                 final String table = r.getMetaData().getTableName(1);
                 r.close();
-                throw new SQLException(Errors.format(Errors.Keys.RecordNotFound_2, table, id));
+                throw new MetadataStoreException(Errors.format(Errors.Keys.RecordNotFound_2, table, id));
             }
             results = r;
             identifier = id;

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -20,9 +20,16 @@ import java.lang.reflect.InvocationHandl
 import java.lang.reflect.Method;
 import java.sql.SQLException;
 import java.util.Collection;
+import java.util.Map;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.metadata.MetadataStandard;
+import org.apache.sis.metadata.KeyNamePolicy;
+import org.apache.sis.metadata.ValueExistencePolicy;
+import org.apache.sis.internal.system.Semaphores;
+import org.apache.sis.internal.metadata.Dependencies;
 
 
 /**
@@ -35,6 +42,16 @@ import org.apache.sis.util.collection.Ba
  * name is translated into a table name, and the method name is translated into a column name.
  * Then the information is fetched in the underlying metadata database.
  *
+ * <p>There is usually a one-to-one correspondence between invoked methods and the columns to be read, but not always.
+ * Some method invocations may actually trig a computation using the values of other columns. This happen for example
+ * when invoking a deprecated method which computes its value from non-deprecated methods. Such situations happen in
+ * the transition from ISO 19115:2003 to ISO 19115:2014 and may happen again in the future as standards are revised.
+ * The algorithms are encoded in implementation classes like the ones in {@link org.apache.sis.metadata.iso} packages,
+ * and access to those implementation classes is enabled by the {@link #cache} field (which, consequently, is more than
+ * only a cache).</p>
+ *
+ * <p>Instance of this class shall be thread-safe.</p>
+ *
  * @author  Touraïvane (IRD)
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 0.8
@@ -57,8 +74,35 @@ final class Dispatcher implements Invoca
     /**
      * Index in the {@code CachedStatement} cache array where to search first. This is only a hint for increasing
      * the chances to find quickly a {@code CachedStatement} instance for the right type and identifier.
+     *
+     * <div class="note"><b>Design note:</b>
+     * this field is declared in this {@code Dispatcher} class instead than {@link CachedStatement} because we need
+     * it before a {@code CachedStatement} instance can be found. Furthermore two {@code Dispatcher} instances may
+     * have different {@code preferredIndex} values even if their {@link CachedStatement#type} value is the same,
+     * since their {@link #identifier} values are different.</div>
+     */
+    byte preferredIndex;
+
+    /**
+     * The metadata instance where to store the property (column) values, or {@code null} if not yet created.
+     * For ISO 19115, this is an instance of one of the classes defined in {@link org.apache.sis.metadata.iso}
+     * package or sub-packages. The intend is not only to cache the property values, but also to leverage
+     * implementations that compute automatically some property values from other properties.
+     * The main usage is computing the value of a deprecated property from the values of non-deprecated ones,
+     * e.g. for transition from ISO 19115:2003 to ISO 19115:2014.
+     */
+    private transient volatile Object cache;
+
+    /**
+     * A bitmask of properties having null values. Cached for avoiding to query the database many times.
+     * Bit indices are given by {@link LookupInfo#asIndexMap(MetadataStandard)}. If a metadata contains
+     * more than 64 properties, no "null value" information will be stored for the extra properties.
+     * No damage will happen except more database accesses than needed.
+     *
+     * <p>We do not need to synchronize this field because it is only an optimization. It is okay if a bit
+     * is wrongly zero; the only consequence is that it will cause one more database access than needed.</p>
      */
-    int preferredIndex;
+    private transient long nullValues;
 
     /**
      * Creates a new metadata handler.
@@ -83,13 +127,11 @@ final class Dispatcher implements Invoca
      */
     @Override
     public Object invoke(final Object proxy, final Method method, final Object[] args) {
-        final Class<?> type = method.getDeclaringClass();
-        final String   name = method.getName();
-        final int      n    = (args != null) ? args.length : 0;
-        switch (name) {
+        final int n = (args != null) ? args.length : 0;
+        switch (method.getName()) {
             case "toString": {
                 if (n != 0) break;
-                return toString(type);
+                return toString(method.getDeclaringClass());
             }
             case "hashCode": {
                 if (n != 0) break;
@@ -105,26 +147,154 @@ final class Dispatcher implements Invoca
             }
             default: {
                 if (n != 0) break;
-                if (!source.standard.isMetadata(type)) break;
                 /*
                  * The invoked method is a method from the metadata interface.
                  * Consequently, the information should exist in the database.
+                 * First, we will check the cache. If the value is not present, we will query the database and
+                 * fetch the cache again (because the class that implement the cache may perform some computation).
                  */
+                Object value;
                 try {
-                    return source.getValue(type, method, this);
-                } catch (SQLException | MetadataStoreException e) {
-                    Class<?> returnType = method.getReturnType();
+                    value = fetchValue(source.getLookupInfo(method.getDeclaringClass()), method);
+                } catch (ReflectiveOperationException | SQLException | MetadataStoreException e) {
+                    throw new BackingStoreException(error(method), e);
+                }
+                /*
+                 * At this point we got the metadata property value, which may be null.
+                 * If the method returns a collection, replace null value by empty set or empty list.
+                 */
+                if (value == null) {
+                    final Class<?> returnType = method.getReturnType();
                     if (Collection.class.isAssignableFrom(returnType)) {
-                        final Class<?> elementType = Classes.boundOfParameterizedProperty(method);
-                        if (elementType != null) {
-                            returnType = elementType;
+                        value = CollectionsExt.empty(returnType);
+                    }
+                }
+                return value;
+            }
+        }
+        /*
+         * Unknown method invoked, or wrong number of arguments.
+         */
+        throw new BackingStoreException(Errors.format(Errors.Keys.UnsupportedOperation_1,
+                    Classes.getShortName(method.getDeclaringClass()) + '.' + method.getName()));
+    }
+
+    /**
+     * Gets, computes or read from the database a metadata property value.
+     * This method returns the first non-null value in the following choices:
+     *
+     * <ol>
+     *   <li>If the property value is present in the {@linkplain #cache}, the cached value.</li>
+     *   <li>If the "cache" can compute the value from other property values, the result of that computation.
+     *       This case happen mostly for deprecated properties that are replaced by one or more newer properties.</li>
+     *   <li>The value stored in the database. The database is queried only once for the requested property
+     *       and the result is cached for future reuse.</li>
+     * </ol>
+     *
+     * @param  info    information related to the <em>interface</em> of the metadata object for which a property
+     *                 value is requested. This is used for fetching information from the {@link MetadataStandard}.
+     * @param  method  the method to be invoked. The class given by {@link Method#getDeclaringClass()} is usually
+     *                 the same than the one given by {@link LookupInfo#getMetadataType()}, but not necessarily.
+     *                 The two classes may differ if the method is declared only in the implementation class.
+     * @return the property value, or {@code null} if none.
+     * @throws ReflectiveOperationException if an error occurred while querying the {@link #cache}.
+     * @throws SQLException if an error occurred while querying the database.
+     * @throws MetadataStoreException if a value was not found or can not be converted to the expected type.
+     */
+    private Object fetchValue(final LookupInfo info, final Method method)
+            throws ReflectiveOperationException, SQLException, MetadataStoreException
+    {
+        Object value = null;
+        final long nullBit = 1L << info.asIndexMap(source.standard).get(method.getName());     // Okay even if overflow.
+        /*
+         * The NULL_COLLECTION semaphore prevents creation of new empty collections by getter methods
+         * (a consequence of lazy instantiation). The intend is to avoid creation of unnecessary objects
+         * for all unused properties. Users should not see behavioral difference.
+         */
+        if ((nullValues & nullBit) == 0) {
+            final Class<?> type = info.getMetadataType();
+            final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
+            try {
+                Object cache = this.cache;
+                if (cache != null) {
+                    synchronized (cache) {
+                        value = method.invoke(cache);
+                    }
+                }
+                if (value == null) {
+                    info.setMetadataType(type);     // Precaution in case method.invoke(cache) fetched other metadata.
+                    value = source.readColumn(info, method, this);
+                    if (value != null) {
+                        if (cache == null) {
+                            final Class<?> impl = source.standard.getImplementation(type);
+                            if (impl == null) {
+                                return value;
+                            }
+                            this.cache = cache = impl.newInstance();
+                            /*
+                             * We do not use AtomicReference because it is okay if the cache is instantiated twice.
+                             * It would cause us to query the database twice, but we should get the same information.
+                             */
+                        }
+                        final Map<String, Object> map = source.standard.asValueMap(cache, type,
+                                    KeyNamePolicy.METHOD_NAME, ValueExistencePolicy.ALL);
+                        synchronized (cache) {
+                            value = map.putIfAbsent(method.getName(), value);
+                            if (value == null) {
+                                value = method.invoke(cache);
+                            }
+                        }
+                    } else {
+                        /*
+                         * If we found no explicit value for the requested property, maybe it is a deprecated property
+                         * computed from other property values and those other properties have not yet been stored in
+                         * the cache object (because that "cache" is also the object computing deprecated properties).
+                         */
+                        final Class<?> impl = source.standard.getImplementation(type);
+                        if (impl != null) {
+                            final Dependencies dependencies = impl.getMethod(method.getName()).getAnnotation(Dependencies.class);
+                            if (dependencies != null) {
+                                boolean hasValue = false;
+                                for (final String dep : dependencies.value()) {
+                                    info.setMetadataType(type);
+                                    hasValue |= (fetchValue(info, impl.getMethod(dep)) != null);
+                                }
+                                if (hasValue) {
+                                    cache = this.cache;             // Created by recursive 'invoke(…)' call above.
+                                    if (cache != null) {
+                                        synchronized (cache) {
+                                            value = method.invoke(cache);             // Attempt a new computation.
+                                        }
+                                    }
+                                }
+                            }
                         }
                     }
-                    throw new BackingStoreException(Errors.format(Errors.Keys.DatabaseError_2, returnType, identifier), e);
                 }
+            } finally {
+                if (!allowNull) {
+                    Semaphores.clear(Semaphores.NULL_COLLECTION);
+                }
+            }
+        }
+        if (value == null) {
+            nullValues |= nullBit;
+        }
+        return value;
+    }
+
+    /**
+     * Returns the error message for a failure to query the database for the property identified by the given method.
+     */
+    final String error(final Method method) {
+        Class<?> returnType = method.getReturnType();
+        if (Collection.class.isAssignableFrom(returnType)) {
+            final Class<?> elementType = Classes.boundOfParameterizedProperty(method);
+            if (elementType != null) {
+                returnType = elementType;
             }
         }
-        throw new BackingStoreException(Errors.format(Errors.Keys.UnsupportedOperation_1, type + "." + name));
+        return Errors.format(Errors.Keys.DatabaseError_2, returnType, identifier);
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Installer.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -25,7 +25,7 @@ import org.apache.sis.util.StringBuilder
 
 
 /**
- * Execute the installation scripts for the "metadata" schema in the "SpatialMetadata" database.
+ * Executes the installation scripts for the "metadata" schema in the "SpatialMetadata" database.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataProxy.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -19,6 +19,8 @@ package org.apache.sis.metadata.sql;
 
 /**
  * Interface for metadata that are implemented by a proxy class.
+ * Instances of this interface are created by calls to {@code Proxy.newProxyInstance(…)}
+ * in {@link MetadataSource#lookup(Class, String)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -34,6 +34,7 @@ import java.util.logging.LogRecord;
 import java.lang.reflect.Array;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.io.IOException;
 import javax.sql.DataSource;
 import java.sql.DatabaseMetaData;
 import java.sql.Connection;
@@ -41,10 +42,10 @@ import java.sql.Statement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLNonTransientException;
-import java.io.IOException;
 import java.sql.PreparedStatement;
 import org.opengis.annotation.UML;
 import org.opengis.util.CodeList;
+import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.KeyNamePolicy;
 import org.apache.sis.metadata.ValueExistencePolicy;
@@ -57,6 +58,7 @@ import org.apache.sis.internal.metadata.
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.collection.CodeListSet;
 import org.apache.sis.util.collection.WeakValueHashMap;
 import org.apache.sis.util.logging.WarningListeners;
@@ -64,9 +66,8 @@ import org.apache.sis.util.logging.Warni
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ObjectConverter;
-import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.UnconvertibleObjectException;
+import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.iso.Types;
 
@@ -84,6 +85,20 @@ import org.apache.sis.util.iso.Types;
  *
  * where {@code id} is the primary key value for the desired record in the {@code MD_Format} table.
  *
+ * <div class="section">Properties</div>
+ * The constructor expects three Java arguments (the {@linkplain MetadataStandard metadata standard},
+ * the {@linkplain DataSource data source} and the database schema) completed by an arbitrary amount
+ * of optional arguments given as a map of properties.
+ * The following keys are recognized by {@code MetadataSource} and all other entries are ignored:
+ *
+ * <table class="sis">
+ *   <caption>Optional properties at construction time</caption>
+ *   <tr><th>Key</th>                     <th>Value type</th>          <th>Description</th></tr>
+ *   <tr><td>{@code "catalog"}</td>       <td>{@link String}</td>      <td>The database catalog where the metadata schema is stored.</td></tr>
+ *   <tr><td>{@code "classloader"}</td>   <td>{@link ClassLoader}</td> <td>The class loader to use for creating {@link Proxy} instances.</td></tr>
+ *   <tr><td>{@code "maxStatements"}</td> <td>{@link Integer}</td>     <td>Maximal number of {@link PreparedStatement}s that can be kept simultaneously open.</td></tr>
+ * </table>
+ *
  * <div class="section">Concurrency</div>
  * {@code MetadataSource} is thread-safe but is not concurrent. If concurrency is desired,
  * multiple instances of {@code MetadataSource} can be created for the same {@link DataSource}.
@@ -97,16 +112,26 @@ import org.apache.sis.util.iso.Types;
  */
 public class MetadataSource implements AutoCloseable {
     /**
-     * The catalog, set to {@code null} for now. This is defined as a constant in order to make easier
-     * to spot the places where catalog would be used, if we want to use it in a future version.
+     * The policy for column names. We use UML identifiers (e.g. {@code "title"}) as defined by the metadata standard
+     * (typically ISO 19115).
      */
-    private static final String CATALOG = null;
+    static final KeyNamePolicy NAME_POLICY = KeyNamePolicy.UML_IDENTIFIER;
 
     /**
      * The column name used for the identifiers. We do not quote this identifier;
      * we will let the database uses its own lower-case / upper-case convention.
      */
-    private static final String ID_COLUMN = "ID";
+    static final String ID_COLUMN = "ID";
+
+    /**
+     * Delimiter characters for the table name in identifier. Table names are prefixed to identifiers only if
+     * the type represented by the table is a subtype. For example since {@code CI_Organisation} is a subtype
+     * of {@code CI_Party}, identifiers for organizations need to be prefixed by {@code {CI_Organisation}} in
+     * order allow {@code MetadataSource} to know in which table to search for such party.
+     *
+     * @see MetadataWriter#isReservedChar(int)
+     */
+    static final char TYPE_OPEN = '{', TYPE_CLOSE = '}';
 
     /**
      * The timeout before to close a prepared statement, in nanoseconds. This is set to 2 seconds,
@@ -181,6 +206,11 @@ public class MetadataSource implements A
     private final CachedStatement[] statements;
 
     /**
+     * The catalog, or {@code null} if none.
+     */
+    final String catalog;
+
+    /**
      * The database schema where metadata are stored, or {@code null} if none. In the metadata source
      * {@linkplain #getProvided() provided by SIS}, this is {@code "metadata"} or {@code "METADATA"},
      * depending on the database convention regarding lower case / upper case identifiers.
@@ -233,17 +263,15 @@ public class MetadataSource implements A
     private final WeakValueHashMap<CacheKey,Object> pool;
 
     /**
-     * The last converter used. This field exists only for performance purposes, on
-     * the assumption that the last used converter has good chances to be used again.
-     *
-     * @see #getValue(Class, Method, Dispatcher)
+     * Some information about last used objects. Cached on assumption that the same information
+     * will be used more than once before to move to another metadata object.
      */
-    private transient volatile ObjectConverter<?,?> lastConverter;
+    private final ThreadLocal<LookupInfo> lastUsed;
 
     /**
      * Where to report the warnings. This is not necessarily a logger, since users can register listeners.
      *
-     * @see #getValue(Class, Method, Dispatcher)
+     * @see #readColumn(LookupInfo, Method, Dispatcher)
      */
     private final WarningListeners<MetadataSource> listeners;
 
@@ -292,7 +320,7 @@ public class MetadataSource implements A
             synchronized (MetadataSource.class) {
                 ms = instance;
                 if (ms == null) {
-                    ms = new MetadataSource(MetadataStandard.ISO_19115, dataSource, "metadata", null, null);
+                    ms = new MetadataSource(MetadataStandard.ISO_19115, dataSource, "metadata", null);
                     ms.install();
                     instance = ms;
                 }
@@ -307,25 +335,29 @@ public class MetadataSource implements A
      * the database source are mandatory information.
      * All other information are optional and can be {@code null}.
      *
-     * @param  standard       the metadata standard to implement.
-     * @param  dataSource     the source for getting a connection to the database.
-     * @param  schema         the database schema were metadata tables are stored, or {@code null} if none.
-     * @param  classloader    the class loader to use for creating {@link Proxy} instances, or {@code null} for the default.
-     * @param  maxStatements  maximal number of {@link PreparedStatement}s that can be kept simultaneously open,
-     *                        or {@code null} for a default value.
+     * @param  standard    the metadata standard to implement.
+     * @param  dataSource  the source for getting a connection to the database.
+     * @param  schema      the database schema were metadata tables are stored, or {@code null} if none.
+     * @param  properties  additional options, or {@code null} if none. See class javadoc for a description.
      */
     public MetadataSource(final MetadataStandard standard, final DataSource dataSource,
-            final String schema, ClassLoader classloader, Integer maxStatements)
+            final String schema, final Map<String,?> properties)
     {
         ArgumentChecks.ensureNonNull("standard",   standard);
         ArgumentChecks.ensureNonNull("dataSource", dataSource);
+        ClassLoader classloader;
+        Integer maxStatements;
+
+        catalog       = Containers.property(properties, "catalog",       String.class);
+        classloader   = Containers.property(properties, "classloader",   ClassLoader.class);
+        maxStatements = Containers.property(properties, "maxStatements", Integer.class);
         if (classloader == null) {
             classloader = getClass().getClassLoader();
         }
         if (maxStatements == null) {
             maxStatements = 10;               // Default value, may change in any future Apache SIS version.
         } else {
-            ArgumentChecks.ensureBetween("maxStatements", 2, 100, maxStatements);       // Arbitrary limits.
+            ArgumentChecks.ensureBetween("maxStatements", 2, 0xFF, maxStatements);   // Unsigned byte range.
         }
         this.standard     = standard;
         this.dataSource   = dataSource;
@@ -336,6 +368,7 @@ public class MetadataSource implements A
         this.tableColumns = new HashMap<>();
         this.pool         = new WeakValueHashMap<>(CacheKey.class);
         this.listeners    = new WarningListeners<>(this);
+        this.lastUsed     = ThreadLocal.withInitial(LookupInfo::new);
     }
 
     /**
@@ -354,12 +387,14 @@ public class MetadataSource implements A
         ArgumentChecks.ensureNonNull("source", source);
         standard     = source.standard;
         dataSource   = source.dataSource;
+        catalog      = source.catalog;
         schema       = source.schema;
         quoteSchema  = source.quoteSchema;
         statements   = new CachedStatement[source.statements.length];
         tableColumns = new HashMap<>();
         classloader  = source.classloader;
         pool         = source.pool;
+        lastUsed     = source.lastUsed;
         listeners    = new WarningListeners<>(this, source.listeners);
     }
 
@@ -384,7 +419,7 @@ public class MetadataSource implements A
                 schema = schema.toLowerCase(Locale.US);
             }
             quoteSchema = false;
-            try (ResultSet result = md.getTables(CATALOG, schema, "CI_Citation", null)) {
+            try (ResultSet result = md.getTables(catalog, schema, "CI_Citation", null)) {
                 if (result.next()) {
                     return;
                 }
@@ -392,7 +427,11 @@ public class MetadataSource implements A
             final Installer installer = new Installer(connection);
             installer.run();
         } catch (IOException | SQLException e) {
-            throw new MetadataStoreException(e);
+            /*
+             * Derby sometime wraps SQLException into another SQLException.  For making the stack strace a
+             * little bit simpler, keep only the root cause provided that the exception type is compatible.
+             */
+            throw new MetadataStoreException(e.getLocalizedMessage(), Exceptions.unwrap(e));
         }
     }
 
@@ -408,7 +447,7 @@ public class MetadataSource implements A
      * @return the connection to the database.
      * @throws SQLException if an error occurred while fetching the connection.
      */
-    private Connection connection() throws SQLException {
+    final Connection connection() throws SQLException {
         assert Thread.holdsLock(this);
         Connection c = connection;
         if (c == null) {
@@ -420,9 +459,16 @@ public class MetadataSource implements A
     }
 
     /**
+     * Returns the database schema where metadata are stored, or {@code null} if none.
+     */
+    final String schema() {
+        return schema;
+    }
+
+    /**
      * Returns a helper class for building SQL statements.
      */
-    private SQLBuilder helper() throws SQLException {
+    final SQLBuilder helper() throws SQLException {
         assert Thread.holdsLock(this);
         if (helper == null) {
             helper = new SQLBuilder(connection().getMetaData(), quoteSchema);
@@ -498,7 +544,7 @@ public class MetadataSource implements A
      * Returns the table name for the specified class.
      * This is usually the ISO 19115 name.
      */
-    private static String getTableName(final Class<?> type) {
+    static String getTableName(final Class<?> type) {
         final UML annotation = type.getAnnotation(UML.class);
         if (annotation == null) {
             return type.getSimpleName();
@@ -508,25 +554,13 @@ public class MetadataSource implements A
     }
 
     /**
-     * Returns the column name for the specified method.
-     */
-    private static String getColumnName(final Method method) {
-        final UML annotation = method.getAnnotation(UML.class);
-        if (annotation == null) {
-            return method.getName();
-        }
-        final String name = annotation.identifier();
-        return name.substring(name.lastIndexOf('.') + 1);
-    }
-
-    /**
      * If the given metadata is a proxy generated by this {@code MetadataSource}, returns the
      * identifier of that proxy. Such metadata do not need to be inserted again in the database.
      *
      * @param  metadata  the metadata to test.
      * @return the identifier (primary key), or {@code null} if the given metadata is not a proxy.
      */
-    private String proxy(final Object metadata) {
+    final String proxy(final Object metadata) {
         return (metadata instanceof MetadataProxy) ? ((MetadataProxy) metadata).identifier(this) : null;
     }
 
@@ -540,8 +574,8 @@ public class MetadataSource implements A
      * @throws ClassCastException if the metadata object does not implement a metadata interface
      *         of the expected package.
      */
-    private Map<String,Object> asMap(final Object metadata) throws ClassCastException {
-        return standard.asValueMap(metadata, null, KeyNamePolicy.UML_IDENTIFIER, ValueExistencePolicy.ALL);
+    final Map<String,Object> asValueMap(final Object metadata) throws ClassCastException {
+        return standard.asValueMap(metadata, null, NAME_POLICY, ValueExistencePolicy.ALL);
     }
 
     /**
@@ -552,7 +586,7 @@ public class MetadataSource implements A
      * @return the given value, or its first element if the value is a collection,
      *         or {@code null} if the given value is null or an empty collection.
      */
-    private static Object extractFromCollection(Object value) {
+    static Object extractFromCollection(Object value) {
         while (value instanceof Iterable<?>) {
             final Iterator<?> it = ((Iterable<?>) value).iterator();
             if (!it.hasNext()) {
@@ -581,14 +615,16 @@ public class MetadataSource implements A
              * be present in the database in order to ensure foreigner key constraints, but
              * those tables are not used in any way by the org.apache.sis.metadata.sql package.
              */
-            if (metadata instanceof CodeList<?>) {
-                identifier = ((CodeList<?>) metadata).name();
+            if (metadata instanceof ControlledVocabulary) {
+                identifier = Types.getCodeName((ControlledVocabulary) metadata);
+            } else if (metadata instanceof Enum<?>) {
+                identifier = ((Enum<?>) metadata).name();
             } else {
                 final String table;
                 final Map<String,Object> asMap;
                 try {
                     table = getTableName(standard.getInterface(metadata.getClass()));
-                    asMap = asMap(metadata);
+                    asMap = asValueMap(metadata);
                 } catch (ClassCastException e) {
                     throw new MetadataStoreException(Errors.format(
                             Errors.Keys.IllegalArgumentClass_2, "metadata", metadata.getClass()));
@@ -597,7 +633,7 @@ public class MetadataSource implements A
                     try (Statement stmt = connection().createStatement()) {
                         identifier = search(table, null, asMap, stmt, helper());
                     } catch (SQLException e) {
-                        throw new MetadataStoreException(e);
+                        throw new MetadataStoreException(e.getLocalizedMessage(), Exceptions.unwrap(e));
                     }
                 }
             }
@@ -617,7 +653,7 @@ public class MetadataSource implements A
      * @return the identifier of the given metadata, or {@code null} if none.
      * @throws SQLException if an error occurred while searching in the database.
      */
-    private String search(final String table, Set<String> columns, final Map<String,Object> metadata,
+    final String search(final String table, Set<String> columns, final Map<String,Object> metadata,
             final Statement stmt, final SQLBuilder helper) throws SQLException
     {
         assert Thread.holdsLock(this);
@@ -644,8 +680,10 @@ public class MetadataSource implements A
              * Note that if a metadata dependency is not found, we can stop the whole process immediately.
              */
             if (value != null) {
-                if (value instanceof CodeList<?>) {
-                    value = ((CodeList<?>) value).name();
+                if (value instanceof ControlledVocabulary) {
+                    value = Types.getCodeName((ControlledVocabulary) value);
+                } else if (value instanceof Enum<?>) {
+                    value = ((Enum<?>) value).name();
                 } else {
                     String dependency = proxy(value);
                     if (dependency != null) {
@@ -654,7 +692,7 @@ public class MetadataSource implements A
                         final Class<?> type = value.getClass();
                         if (standard.isMetadata(type)) {
                             dependency = search(getTableName(standard.getInterface(type)),
-                                    null, asMap(value), stmt, new SQLBuilder(helper));
+                                    null, asValueMap(value), stmt, new SQLBuilder(helper));
                             if (dependency == null) {
                                 return null;                    // Dependency not found.
                             }
@@ -687,7 +725,7 @@ public class MetadataSource implements A
                     if (identifier == null) {
                         identifier = candidate;
                     } else if (!identifier.equals(candidate)) {
-                        warning("search", resources().getLogRecord(
+                        warning(MetadataSource.class, "search", Errors.getResources((Locale) null).getLogRecord(
                                 Level.WARNING, Errors.Keys.DuplicatedElement_1, candidate));
                         break;
                     }
@@ -709,7 +747,7 @@ public class MetadataSource implements A
      * @return the set of columns, or an empty set if the table has not yet been created.
      * @throws SQLException if an error occurred while querying the database.
      */
-    private Set<String> getExistingColumns(final String table) throws SQLException {
+    final Set<String> getExistingColumns(final String table) throws SQLException {
         assert Thread.holdsLock(this);
         Set<String> columns = tableColumns.get(table);
         if (columns == null) {
@@ -721,7 +759,7 @@ public class MetadataSource implements A
              * on the search path specified in the database environment variables.
              */
             final DatabaseMetaData md = connection().getMetaData();
-            try (ResultSet rs = md.getColumns(CATALOG, schema, table, null)) {
+            try (ResultSet rs = md.getColumns(catalog, schema, table, null)) {
                 while (rs.next()) {
                     if (!columns.add(rs.getString("COLUMN_NAME"))) {
                         // Paranoiac check, but should never happen.
@@ -735,60 +773,129 @@ public class MetadataSource implements A
     }
 
     /**
+     * If the given identifier specifies a subtype of the given type, then returns that subtype.
+     * For example if the given type is {@code Party.class} and the given identifier is
+     * {@code "{CI_Organisation}EPSG"}, then this method returns {@code Organisation.class}.
+     * Otherwise this method returns {@code type} unchanged.
+     */
+    private static Class<?> subType(Class<?> type, final String identifier) {
+        if (identifier.charAt(0) == TYPE_OPEN) {
+            final int i = identifier.indexOf(TYPE_CLOSE);
+            if (i >= 0) {
+                final Class<?> subType = Types.forStandardName(identifier.substring(1, i));
+                if (subType != null && type.isAssignableFrom(subType)) {
+                    type = subType;
+                }
+            }
+        }
+        return type;
+    }
+
+    /**
      * Returns an implementation of the specified metadata interface filled with the data referenced
-     * by the specified identifier. Alternatively, this method can also returns a {@link CodeList} element.
+     * by the specified identifier. Alternatively, this method can also return a {@link CodeList} or
+     * {@link Enum} element.
      *
      * @param  <T>         the parameterized type of the {@code type} argument.
      * @param  type        the interface to implement (e.g. {@link org.opengis.metadata.citation.Citation}),
-     *                     or the {@link CodeList} type.
+     *                     or the {@link ControlledVocabulary} type ({@link CodeList} or some {@link Enum}).
      * @param  identifier  the identifier of the record for the metadata entity to be created.
      *                     This is usually the primary key of the record to search for.
      * @return an implementation of the required interface, or the code list element.
      * @throws MetadataStoreException if a SQL query failed.
      */
-    public <T> T lookup(final Class<T> type, String identifier) throws MetadataStoreException {
+    public <T> T lookup(final Class<T> type, final String identifier) throws MetadataStoreException {
         ArgumentChecks.ensureNonNull("type", type);
-        ArgumentChecks.ensureNonNull("identifier", identifier);
-        /*
-         * IMPLEMENTATION NOTE: This method must not invoke any method which may access 'statements'.
-         * It is not allowed to acquire the lock on 'statements' neither.
-         */
+        ArgumentChecks.ensureNonEmpty("identifier", identifier);
         Object value;
-        if (CodeList.class.isAssignableFrom(type)) {
+        if (ControlledVocabulary.class.isAssignableFrom(type)) {
             value = getCodeList(type, identifier);
         } else {
             final CacheKey key = new CacheKey(type, identifier);
+            /*
+             * IMPLEMENTATION NOTE: be careful to not invoke any method that may synchronize on 'this'
+             * inside the block synchronized on 'pool'.
+             */
             synchronized (pool) {
                 value = pool.get(key);
-                if (value == null) {
+                if (value == null && type.isInterface()) {
                     value = Proxy.newProxyInstance(classloader,
                             new Class<?>[] {type, MetadataProxy.class}, new Dispatcher(identifier, this));
                     pool.put(key, value);
                 }
             }
+            /*
+             * At this point, a null value means that the given type is a class rather than an interface.
+             * This may happen when a new type defined by a standard has not yet been defined in GeoAPI.
+             * In such case, we only have the implementation class in Apache SIS, not yet the interface.
+             * Since we can not create a Proxy, we have to fetch all property values now. This is not
+             * very efficient and may waste a little bit of memory, but it should not happen too often.
+             */
+            if (value == null) {
+                Method method = null;
+                final Class<?> subType = subType(type, identifier);
+                final Dispatcher toSearch = new Dispatcher(identifier, this);
+                try {
+                    value = subType.newInstance();
+                    final LookupInfo info            = getLookupInfo(subType);
+                    final Map<String,Object> map     = asValueMap(value);
+                    final Map<String,String> methods = standard.asNameMap(subType, NAME_POLICY, KeyNamePolicy.METHOD_NAME);
+                    for (final Map.Entry<String,Object> entry : map.entrySet()) {
+                        method = subType.getMethod(methods.get(entry.getKey()));
+                        info.setMetadataType(subType);
+                        final Object p = readColumn(info, method, toSearch);
+                        if (p != null) {
+                            entry.setValue(p);
+                        }
+                    }
+                } catch (ReflectiveOperationException e) {
+                    throw new MetadataStoreException(Errors.format(Errors.Keys.UnsupportedImplementation_1, subType), e);
+                } catch (SQLException e) {
+                    throw new MetadataStoreException(toSearch.error(method), e);
+                }
+            }
         }
         return type.cast(value);
     }
 
     /**
+     * Gets the {@link LookupInfo} instance for call to the {@link #readColumn(LookupInfo, Method, Dispatcher)} method.
+     * The call to those two methods must be in the same thread, and no other metadata object shall be queried between
+     * the two calls (unless {@link LookupInfo#setMetadataType(Class)} is invoked again).
+     *
+     * @param  type  the interface class. This is mapped to the table name in the database.
+     */
+    final LookupInfo getLookupInfo(final Class<?> type) {
+        final LookupInfo info = lastUsed.get();
+        info.setMetadataType(type);
+        return info;
+    }
+
+    /**
      * Invoked by {@link MetadataProxy} for fetching an attribute value from a table.
      *
-     * @param  type      the interface class. This is mapped to the table name in the database.
+     * @param  info      the interface type (together with cached information).
+     *                   This is mapped to the table name in the database.
      * @param  method    the method invoked. This is mapped to the column name in the database.
      * @param  toSearch  contains the identifier and preferred index of the record to search.
      * @return the value of the requested attribute.
      * @throws SQLException if the SQL query failed.
-     * @throws MetadataStoreException if a value can not be converted to the expected type.
+     * @throws MetadataStoreException if a value was not found or can not be converted to the expected type.
      */
-    final Object getValue(final Class<?> type, final Method method, final Dispatcher toSearch)
+    final Object readColumn(final LookupInfo info, final Method method, final Dispatcher toSearch)
             throws SQLException, MetadataStoreException
     {
+        /*
+         * If the identifier is prefixed with a table name as in "{CI_Organisation}identifier",
+         * the name between bracket is a subtype of the given 'type' argument.
+         */
+        final Class<?> type           = subType(info.getMetadataType(), toSearch.identifier);
         final Class<?> returnType     = method.getReturnType();
         final boolean  wantCollection = Collection.class.isAssignableFrom(returnType);
         final Class<?> elementType    = wantCollection ? Classes.boundOfParameterizedProperty(method) : returnType;
         final boolean  isMetadata     = standard.isMetadata(elementType);
         final String   tableName      = getTableName(type);
-        final String   columnName     = getColumnName(method);
+        final String   columnName     = info.asNameMap(standard).get(method.getName());
         final boolean  isArray;
         Object value;
         synchronized (this) {
@@ -801,7 +908,7 @@ public class MetadataSource implements A
                  * Note that the usage of 'result' must stay inside this synchronized block
                  * because we can not assume that JDBC connections are thread-safe.
                  */
-                CachedStatement result = take(type, toSearch.preferredIndex);
+                CachedStatement result = take(type, Byte.toUnsignedInt(toSearch.preferredIndex));
                 if (result == null) {
                     final SQLBuilder helper = helper();
                     final String query = helper.clear().append("SELECT * FROM ")
@@ -816,7 +923,7 @@ public class MetadataSource implements A
                     value = array.getArray();
                     array.free();
                 }
-                toSearch.preferredIndex = recycle(result, toSearch.preferredIndex);
+                toSearch.preferredIndex = (byte) recycle(result, Byte.toUnsignedInt(toSearch.preferredIndex));
             }
         }
         /*
@@ -831,7 +938,7 @@ public class MetadataSource implements A
                     if (isMetadata) {
                         element = lookup(elementType, element.toString());
                     } else try {
-                        element = convert(elementType, element);
+                        element = info.convert(elementType, element);
                     } catch (UnconvertibleObjectException e) {
                         throw new MetadataStoreException(Errors.format(Errors.Keys.IllegalPropertyValueClass_3,
                                 columnName + '[' + i + ']', elementType, element.getClass()), e);
@@ -845,26 +952,15 @@ public class MetadataSource implements A
             }
         }
         /*
-         * Now converts the value to its final type, including conversion of null
-         * value to empty collections if the return value should be a collection.
+         * Now converts the value to its final type. To be strict, we should convert null values into empty collections
+         * if the return type is a collection type. But we leave this task to the caller (which is the Dispatcher class)
+         * for making easier to detect when a value is absent, for allowing Dispatcher to manage its cache.
          */
-        if (value == null) {
-            if (wantCollection) {
-                if (Set.class.isAssignableFrom(returnType)) {
-                    if (SortedSet.class.isAssignableFrom(returnType)) {
-                        return Collections.emptySortedSet();
-                    } else {
-                        return Collections.EMPTY_SET;
-                    }
-                } else {
-                    return Collections.EMPTY_LIST;
-                }
-            }
-        } else {
+        if (value != null) {
             if (isMetadata) {
                 value = lookup(elementType, value.toString());
             } else try {
-                value = convert(elementType, value);
+                value = info.convert(elementType, value);
             } catch (UnconvertibleObjectException e) {
                 throw new MetadataStoreException(Errors.format(Errors.Keys.IllegalPropertyValueClass_3,
                         columnName, elementType, value.getClass()), e);
@@ -881,34 +977,16 @@ public class MetadataSource implements A
     }
 
     /**
-     * Converts the specified non-metadata value into an object of the expected type.
-     * The expected value is an instance of a class outside the metadata package, for example
-     * {@link String}, {@link org.opengis.util.InternationalString}, {@link java.net.URI}, <i>etc.</i>
-     *
-     * @throws UnconvertibleObjectException if the value can not be converter.
+     * Returns the code of the given type and name. This method is defined for avoiding the compiler warning
+     * message when the actual class is unknown (it must have been checked dynamically by the caller however).
      */
-    @SuppressWarnings({"unchecked","rawtypes"})
-    private Object convert(final Class<?> targetType, Object value) throws UnconvertibleObjectException {
-        final Class<?> sourceType = value.getClass();
-        if (!targetType.isAssignableFrom(sourceType)) {
-            ObjectConverter converter = lastConverter;
-            if (converter == null || !converter.getSourceClass().isAssignableFrom(sourceType) ||
-                                     !targetType.isAssignableFrom(converter.getTargetClass()))
-            {
-                lastConverter = converter = ObjectConverters.find(sourceType, targetType);
-            }
-            value = converter.apply(value);
+    @SuppressWarnings("unchecked")
+    private static ControlledVocabulary getCodeList(final Class<?> type, final String name) {
+        if (type.isEnum()) {
+            return (ControlledVocabulary) Types.forEnumName(type.asSubclass(Enum.class), name);
+        } else {
+            return Types.forCodeName(type.asSubclass(CodeList.class), name, true);
         }
-        return value;
-    }
-
-    /**
-     * Returns the code of the given type and name. This method is defined for avoiding the warning message
-     * when the actual class is unknown (it must have been checked dynamically by the caller however).
-     */
-    @SuppressWarnings({"unchecked","rawtypes"})
-    private static CodeList<?> getCodeList(final Class<?> type, final String name) {
-        return Types.forCodeName((Class) type, name, true);
     }
 
     /**
@@ -959,20 +1037,14 @@ public class MetadataSource implements A
     }
 
     /**
-     * Returns the resources for warnings and error messages.
-     */
-    private static Errors resources() {
-        return Errors.getResources((Locale) null);
-    }
-
-    /**
      * Reports a warning.
      *
+     * @param source  the source class, either {@code MetadataSource} or {@code MetadataWriter}.
      * @param method  the method to report as the warning emitter.
      * @param record  the warning to report.
      */
-    private void warning(final String method, final LogRecord record) {
-        record.setSourceClassName(MetadataSource.class.getCanonicalName());
+    final void warning(final Class<? extends MetadataSource> source, final String method, final LogRecord record) {
+        record.setSourceClassName(source.getCanonicalName());
         record.setSourceMethodName(method);
         record.setLoggerName(Loggers.SQL);
         listeners.warning(record);
@@ -1107,7 +1179,7 @@ public class MetadataSource implements A
              */
             final LogRecord record = new LogRecord(Level.WARNING, e.toString());
             record.setThrown(e);
-            warning("closeExpired", record);
+            warning(MetadataSource.class, "closeExpired", record);
         }
     }
 
@@ -1132,7 +1204,7 @@ public class MetadataSource implements A
             }
             helper = null;
         } catch (SQLException e) {
-            throw new MetadataStoreException(e);
+            throw new MetadataStoreException(e.getLocalizedMessage(), Exceptions.unwrap(e));
         }
     }
 }

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataStoreException.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.metadata.sql;
 
-import org.opengis.util.FactoryException;
-
 
 /**
  * Thrown when a metadata access failed.
@@ -29,7 +27,7 @@ import org.opengis.util.FactoryException
  * @since   0.8
  * @module
  */
-public class MetadataStoreException extends FactoryException {
+public class MetadataStoreException extends Exception {
     /**
      * For cross-version compatibility.
      */

Modified: sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/package-info.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/package-info.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/package-info.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -33,17 +33,20 @@
  *   <li>{@link org.opengis.util.InternationalString} are stored only for the default locale.</li>
  *   <li>Cyclic graph (<var>A</var> references <var>B</var> which reference <var>A</var>) are not supported,
  *       unless foreigner key constraints are manually disabled for the columns which contain the cyclic references.</li>
- *   <li>Metadata that are sub-interface of other metadata (for example
- *       {@link org.opengis.metadata.extent.GeographicDescription} which extends
- *       {@link org.opengis.metadata.extent.GeographicExtent}) can be stored only
- *       in databases supporting <cite>table inheritance</cite>, like
- *       <a href="http://www.postgresql.org">PostgreSQL</a>.</li>
  * </ul>
  *
+ * If the database supports <cite>table inheritance</cite> (like <a href="http://www.postgresql.org">PostgreSQL</a>),
+ * then this package will leverage that feature for the storage of metadata that are sub-interface of other metadata
+ * (for example {@link org.opengis.metadata.extent.GeographicDescription} which extends
+ * {@link org.opengis.metadata.extent.GeographicExtent}).
+ *
  * @author  Touraïvane (IRD)
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 0.8
- * @since   0.8
+ *
+ * @see org.apache.sis.referencing.factory.sql
+ *
+ * @since 0.8
  * @module
  */
 package org.apache.sis.metadata.sql;

Modified: sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/ScriptRunnerTest.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -42,7 +42,7 @@ public final strictfp class ScriptRunner
      */
     @Test
     public void testOnDerby() throws Exception {
-        final DataSource ds = TestDatabase.create("temporary");
+        final DataSource ds = TestDatabase.create("ScriptRunner");
         try (Connection c = ds.getConnection()) {
             final ScriptRunner sr = new ScriptRunner(c, 3);
             testSupportedFlags(sr);

Modified: sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/TestDatabase.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/TestDatabase.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/TestDatabase.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/sql/TestDatabase.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -30,6 +30,29 @@ import static org.junit.Assume.*;
  * Utility methods for creating temporary databases with Derby.
  * The databases are in-memory only.
  *
+ * <div class="section">Inspecting the database content in a debugger</div>
+ * Make sure that the classpath contains the {@code derbynet.jar} file in addition to {@code derby.jar}.
+ * Then, specify the following options to the JVM (replace the 1527 port number by something else if needed):
+ *
+ * {@preformat text
+ *   -Dderby.drda.startNetworkServer=true
+ *   -Dderby.drda.portNumber=1527
+ * }
+ *
+ * When the application is running, one can verify that the Derby server is listening:
+ *
+ * {@preformat text
+ *   netstat -an | grep "1527"
+ * }
+ *
+ * To connect to the in-memory database, use the {@code "jdbc:derby://localhost:1527/dbname"} URL
+ * (replace {@code "dbname"} by the actual database name.
+ *
+ * <p><b>References:</b>
+ * <ul>
+ *   <li><a href="https://db.apache.org/derby/docs/10.2/adminguide/radminembeddedserverex.html">Embedded server example</a></li>
+ * </ul>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.7
  * @since   0.7

Modified: sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -20,12 +20,15 @@ import java.util.Date;
 import java.util.Locale;
 import java.util.Random;
 import java.util.Collection;
+import java.util.Map;
+import java.lang.reflect.Method;
 import org.opengis.util.CodeList;
 import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.internal.metadata.Dependencies;
 import org.apache.sis.test.AnnotationsTestCase;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOn;
@@ -45,7 +48,7 @@ import static org.opengis.test.Assert.*;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 0.8
  * @since   0.3
  * @module
  */
@@ -112,7 +115,7 @@ public abstract strictfp class MetadataT
      * @param  type      the type of value to create.
      * @return the value of the given {@code type}, or of a type convertible to the given type.
      */
-    protected Object valueFor(final String property, final Class<?> type) {
+    protected Object sampleValueFor(final String property, final Class<?> type) {
         if (CharSequence.class.isAssignableFrom(type)) {
             return "Dummy value for " + property + '.';
         }
@@ -266,7 +269,7 @@ public abstract strictfp class MetadataT
                 fail("Non writable property: " + accessor + '.' + property);
             }
             if (isWritable) {
-                final Object newValue = valueFor(property, elementType);
+                final Object newValue = sampleValueFor(property, elementType);
                 final Object oldValue = accessor.set(i, instance, newValue, PropertyAccessor.RETURN_PREVIOUS);
                 assertEquals("PropertyAccessor.set(…) shall return the value previously returned by get(…).", value, oldValue);
                 value = accessor.get(i, instance);
@@ -301,4 +304,83 @@ public abstract strictfp class MetadataT
               (implementation == org.apache.sis.metadata.iso.citation.DefaultResponsibleParty.class &&
                method.equals("getParties"));
     }
+
+    /**
+     * Verifies the {@link TitleProperty} annotations. This method verifies that the property exist,
+     * is a singleton, and is not another metadata object. The property should also be mandatory,
+     * but this method does not verify that restriction since there is some exceptions.
+     *
+     * @since 0.8
+     */
+    @Test
+    public void testTitlePropertyAnnotation() {
+        for (final Class<?> type : types) {
+            final Class<?> impl = standard.getImplementation(type);
+            if (impl != null) {
+                final TitleProperty an = impl.getAnnotation(TitleProperty.class);
+                if (an != null) {
+                    final String name = an.name();
+                    final String message = impl.getSimpleName() + '.' + name;
+                    final PropertyAccessor accessor = new PropertyAccessor(standard.getCitation(), type, impl, impl);
+
+                    // Property shall exist.
+                    final int index = accessor.indexOf(name, false);
+                    assertTrue(message, index >= 0);
+
+                    // Property can not be a metadata.
+                    final Class<?> elementType = accessor.type(index, TypeValuePolicy.ELEMENT_TYPE);
+                    assertFalse(message, standard.isMetadata(elementType));
+
+                    // Property shall be a singleton.
+                    assertSame(message, elementType, accessor.type(index, TypeValuePolicy.PROPERTY_TYPE));
+                }
+            }
+        }
+    }
+
+    /**
+     * Verifies the {@link Dependencies} annotations. This method verifies that the annotation is applied on
+     * deprecated getter methods, that the referenced properties exist and the getter methods of referenced
+     * properties are not deprecated.
+     *
+     * @throws NoSuchMethodException if {@link PropertyAccessor} references a non-existent method (would be a bug).
+     *
+     * @since 0.8
+     */
+    @Test
+    public void testDependenciesAnnotation() throws NoSuchMethodException {
+        for (final Class<?> type : types) {
+            final Class<?> impl = standard.getImplementation(type);
+            if (impl != null) {
+                Map<String,String> names = null;
+                for (final Method method : impl.getDeclaredMethods()) {
+                    final Dependencies dep = method.getAnnotation(Dependencies.class);
+                    if (dep != null) {
+                        final String name = method.getName();
+                        if (names == null) {
+                            names = standard.asNameMap(type, KeyNamePolicy.JAVABEANS_PROPERTY, KeyNamePolicy.METHOD_NAME);
+                        }
+                        /*
+                         * Currently, @Dependencies is applied only on deprecated getter methods.
+                         * However this policy may change in future Apache SIS versions.
+                         */
+                        assertTrue(name, name.startsWith("get"));
+                        assertTrue(name, method.isAnnotationPresent(Deprecated.class));
+                        /*
+                         * All dependencies shall be non-deprecated methods. Combined with above
+                         * restriction about @Dependencies applied only on deprected methods, this
+                         * ensure that there is no cycle.
+                         */
+                        for (final String ref : dep.value()) {
+                            // Verify that the dependency is a property name.
+                            assertEquals(name, names.get(ref), ref);
+
+                            // Verify that the referenced method is non-deprecated.
+                            assertFalse(name, impl.getMethod(names.get(ref)).isAnnotationPresent(Deprecated.class));
+                        }
+                    }
+                }
+            }
+        }
+    }
 }



Mime
View raw message