sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1633643 - in /sis/branches/JDK7: ./ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/test/java/org/apache/sis/feature/ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src/main/...
Date Wed, 22 Oct 2014 15:53:16 GMT
Author: desruisseaux
Date: Wed Oct 22 15:53:16 2014
New Revision: 1633643

URL: http://svn.apache.org/r1633643
Log:
Merge from the JDK8 branch:
  - complete the support of cyclic feature associations
  - avoid usage of some deprecated metadata methods

Added:
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Standards.java
      - copied unchanged from r1633642, sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Standards.java
Modified:
    sis/branches/JDK7/   (props changed)
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Names.java
    sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
    sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
    sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/ConformanceTest.java
    sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java

Propchange: sis/branches/JDK7/
------------------------------------------------------------------------------
  Merged /sis/branches/JDK8:r1633035-1633642

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -17,6 +17,9 @@
 package org.apache.sis.feature;
 
 import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.resources.Errors;
@@ -78,19 +81,6 @@ public class DefaultAssociationRole exte
     private volatile transient String titleProperty;
 
     /**
-     * {@code true} if we determined that the feature type name in this association has been resolved.
-     * A value of {@code true} means that {@link #valueType} is a resolved feature type. However a value
-     * of {@code false} only means that we are not sure, and that we should check again next time.
-     *
-     * <div class="note"><b>Note:</b>
-     * Strictly speaking, this field should be declared {@code volatile} since the names could be
-     * resolved late after construction, after the {@code DefaultAssociationRole} instance became
-     * used by different threads. However this is not the intended usage of deferred associations.
-     * </div>
-     */
-    private transient boolean isResolved;
-
-    /**
      * Constructs an association to the given feature type. The properties map is given unchanged to
      * the {@linkplain AbstractIdentifiedType#AbstractIdentifiedType(Map) super-class constructor}.
      * The following table is a reminder of main (not all) recognized map entries:
@@ -140,19 +130,34 @@ public class DefaultAssociationRole exte
 
     /**
      * Constructs an association to a feature type of the given name.
-     * This constructor shall be used only when creating a cyclic graph of {@link DefaultFeatureType} instances.
+     * This constructor can be used when creating a cyclic graph of {@link DefaultFeatureType} instances.
+     * In such cases, at least one association needs to be created while its {@code FeatureType} is not yet available.
      *
      * <div class="note"><b>Example:</b>
-     * A code first creates {@code FeatureType} <var>A</var>, then {@code FeatureType} <var>B</var>.
-     * It is easy to define an association from <var>B</var> to <var>A</var> since the later exists
-     * at <var>B</var> creation time. But if one wants to define also the converse association, i.e.
-     * from <var>A</var> to <var>B</var>, then it is not possible to create <var>A</var> with the
-     * {@linkplain #DefaultAssociationRole(Map, FeatureType, int, int) above constructor} since
-     * <var>B</var> does not yet exist. We can only give the <em>name</em> of <var>B</var> and
-     * let {@link DefaultFeatureType} substitutes that name by the actual instance when the later
-     * will be known.
+     * The following establishes a bidirectional association between feature types <var>A</var> and <var>B</var>:
+     *
+     * {@preformat java
+     *   String    namespace = "My model";
+     *   GenericName nameOfA = Names.createTypeName(namespace, ":", "Feature type A");
+     *   GenericName nameOfB = Names.createTypeName(namespace, ":", "Feature type B");
+     *   FeatureType typeA = new DefaultFeatureType(nameOfA, false, null,
+     *       new DefaultAssociationRole(Names.createLocalName("Association to B"), nameOfB),
+     *       // More properties if desired.
+     *   );
+     *   FeatureType typeB = new DefaultFeatureType(nameOfB, false, null,
+     *       new DefaultAssociationRole(Names.createLocalName("Association to A"), featureA),
+     *       // More properties if desired.
+     *   );
+     * }
+     *
+     * After the above code completed, the {@linkplain #getValueType() value type} of "<cite>association to B</cite>"
+     * has been automatically set to the {@code typeB} instance.
      * </div>
      *
+     * Callers shall make sure that the feature types graph will not contain more than one feature of the given name.
+     * If more than one {@code FeatureType} instance of the given name is found at resolution time, the selected one
+     * is undetermined.
+     *
      * @param identification The name and other information to be given to this association role.
      * @param valueType      The name of the type of feature values.
      * @param minimumOccurs  The minimum number of occurrences of the association within its containing entity.
@@ -179,37 +184,50 @@ public class DefaultAssociationRole exte
      * @return {@code true} if this association references a resolved feature type after this method call.
      */
     final boolean resolve(final DefaultFeatureType creating) {
-        boolean resolved = isResolved;
-        if (!resolved) {
-            FeatureType type = valueType;
-            if (type instanceof NamedFeatureType) {
-                type = search(creating, type.getName());
+        FeatureType type = valueType;
+        if (type instanceof NamedFeatureType) {
+            final GenericName name = type.getName();
+            if (name.equals(creating.getName())) {
+                type = creating; // This is the most common case.
+            } else {
+                /*
+                 * The feature that we need to resolve is not the one we just created. Maybe we can find
+                 * this desired feature in an association of the 'creating' feature, instead than beeing
+                 * the 'creating' feature itself. This is a little bit unusual, but not illegal.
+                 */
+                final List<FeatureType> deferred = new ArrayList<>();
+                type = search(creating, name, deferred);
                 if (type == null) {
-                    return false;
+                    /*
+                     * Did not found the desired FeatureType in the 'creating' instance.
+                     * Try harder, by searching recursively in associations of associations.
+                     */
+                    if (deferred.isEmpty() || (type = deepSearch(deferred, name)) == null) {
+                        return false;
+                    }
                 }
-                valueType = type;
-            }
-            isResolved = true; // Necessary for avoiding never-ending loop in case of cycle.
-            try {
-                resolved = creating.resolve(type);
-            } finally {
-                isResolved = resolved;
             }
+            valueType = type;
         }
-        return resolved;
+        return true;
     }
 
     /**
-     * Searches in the given {@code feature} for a feature type of the given name.
+     * Searches in the given {@code feature} for an associated feature type of the given name.
+     * This method does not search recursively in the associations of the associated features.
+     * Such recursive search will be performed by {@link #deepSearch(List, GenericName)} only
+     * if we do not find the desired feature in the most direct way.
+     *
+     * <p>Current implementation does not check that there is no duplicated names.
+     * See {@link #deepSearch(List, GenericName)} for a rational.</p>
      *
      * @param  feature The feature in which to search.
      * @param  name The name of the feature to search.
+     * @param  deferred Where to store {@code FeatureType}s to be eventually used for a deep search.
      * @return The feature of the given name, or {@code null} if none.
      */
-    private static FeatureType search(final FeatureType feature, final GenericName name) {
-        if (name.equals(feature.getName())) {
-            return feature;
-        }
+    @SuppressWarnings("null")
+    private static FeatureType search(final FeatureType feature, final GenericName name, final List<FeatureType> deferred) {
         /*
          * Search only in associations declared in the given feature, not in inherited associations.
          * The inherited associations will be checked in a separated loop below if we did not found
@@ -229,6 +247,7 @@ public class DefaultAssociationRole exte
                 if (name.equals(valueType.getName())) {
                     return valueType;
                 }
+                deferred.add(valueType);
             }
         }
         /*
@@ -238,7 +257,10 @@ public class DefaultAssociationRole exte
          * "covariant return type" in the Java language.
          */
         for (FeatureType type : feature.getSuperTypes()) {
-            type = search(type, name);
+            if (name.equals(type.getName())) {
+                return type;
+            }
+            type = search(type, name, deferred);
             if (type != null) {
                 return type;
             }
@@ -247,6 +269,36 @@ public class DefaultAssociationRole exte
     }
 
     /**
+     * Potentially invoked after {@link #search(FeatureType, GenericName, List)} for searching
+     * in associations of associations.
+     *
+     * <p>Current implementation does not check that there is no duplicated names. Even if we did so,
+     * a graph of feature types may have no duplicated names at this time but some duplicated names
+     * later. We rather put a warning in {@link #DefaultAssociationRole(Map, GenericName, int, int)}
+     * javadoc.</p>
+     *
+     * @param  feature The feature in which to search.
+     * @param  name The name of the feature to search.
+     * @param  done The feature types collected by {@link #search(FeatureType, GenericName, List)}.
+     * @return The feature of the given name, or {@code null} if none.
+     */
+    private static FeatureType deepSearch(final List<FeatureType> deferred, final GenericName name) {
+        final Map<FeatureType,Boolean> done = new IdentityHashMap<>(8);
+        for (int i=0; i<deferred.size();) {
+            FeatureType valueType = deferred.get(i++);
+            if (done.put(valueType, Boolean.TRUE) == null) {
+                deferred.subList(0, i).clear(); // Discard previous value for making more room.
+                valueType = search(valueType, name, deferred);
+                if (valueType != null) {
+                    return valueType;
+                }
+                i = 0;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the type of feature values.
      *
      * @return The type of feature values.

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -23,6 +23,7 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.IdentityHashMap;
 import java.util.Collection;
 import java.util.Collections;
 import java.io.IOException;
@@ -128,6 +129,8 @@ public class DefaultFeatureType extends 
      * Strictly speaking, this field should be declared {@code volatile} since the names could
      * be resolved late after construction, after the {@code DefaultFeatureType} instance became
      * used by different threads. However this is not the intended usage of deferred associations.
+     * Furthermore a wrong value ({@code false} when it should be {@code true}) should only cause
+     * more computation than needed, without changing the result.
      * </div>
      */
     private transient boolean isResolved;
@@ -243,7 +246,7 @@ public class DefaultFeatureType extends 
             default: this.properties = UnmodifiableArrayList.wrap(Arrays.copyOf(properties, properties.length, PropertyType[].class)); break;
         }
         computeTransientFields();
-        isResolved = resolve(this, isSimple);
+        isResolved = resolve(this, null, isSimple);
     }
 
     /**
@@ -419,10 +422,11 @@ public class DefaultFeatureType extends 
      * <p>{@code this} shall be the instance in process of being created, not other instance
      * (i.e. recursive method invocations are performed on the same {@code this} instance).</p>
      *
-     * @param  feature The feature type for which to resolve the properties.
+     * @param  feature  The feature type for which to resolve the properties.
+     * @param  previous Previous results, for avoiding never ending loop.
      * @return {@code true} if all names have been resolved.
      */
-    final boolean resolve(final FeatureType feature) {
+    private boolean resolve(final FeatureType feature, final Map<FeatureType,Boolean> previous) {
         /*
          * The isResolved field is used only as a cache for skipping completely the DefaultFeatureType instance if
          * we have determined that there is no unresolved name.  If the given argument is not a DefaultFeatureType
@@ -431,28 +435,52 @@ public class DefaultFeatureType extends 
          */
         if (feature instanceof DefaultFeatureType) {
             final DefaultFeatureType dt = (DefaultFeatureType) feature;
-            return dt.isResolved = resolve(feature, dt.isResolved);
+            return dt.isResolved = resolve(feature, previous, dt.isResolved);
         } else {
-            return resolve(feature, feature.isSimple());
+            return resolve(feature, previous, feature.isSimple());
         }
     }
 
     /**
-     * Implementation of {@link #resolve(FeatureType)}, also to be invoked from the constructor.
+     * Implementation of {@link #resolve(FeatureType, Map)}, also to be invoked from the constructor.
      *
      * @param  feature  The feature type for which to resolve the properties.
+     * @param  previous Previous results, for avoiding never ending loop. Initially {@code null}.
      * @param  resolved {@code true} if we already know that all names are resolved.
      * @return {@code true} if all names have been resolved.
      */
-    private boolean resolve(final FeatureType feature, boolean resolved) {
+    private boolean resolve(final FeatureType feature, Map<FeatureType,Boolean> previous, boolean resolved) {
         if (!resolved) {
             resolved = true;
             for (final FeatureType type : feature.getSuperTypes()) {
-                resolved &= resolve(type);
+                resolved &= resolve(type, previous);
             }
             for (final PropertyType property : feature.getProperties(false)) {
-                if (property instanceof DefaultAssociationRole) {
-                    resolved &= ((DefaultAssociationRole) property).resolve(this);
+                if (property instanceof FeatureAssociationRole) {
+                    if (property instanceof DefaultAssociationRole) {
+                        if (!((DefaultAssociationRole) property).resolve(this)) {
+                            resolved = false;
+                            continue;
+                        }
+                    }
+                    /*
+                     * Resolve recursively the associated features, with a check against infinite recursivity.
+                     * If we fall in a loop (for example A → B → C → A), conservatively returns 'false'. This
+                     * may not be the most accurate answer, but will not cause any more hurt than checking more
+                     * often than necessary.
+                     */
+                    final FeatureType valueType = ((FeatureAssociationRole) property).getValueType();
+                    if (valueType != this) {
+                        if (previous == null) {
+                            previous = new IdentityHashMap<>(8);
+                        }
+                        Boolean r = previous.put(valueType, Boolean.FALSE);
+                        if (r == null) {
+                            r = resolve(valueType, previous);
+                            previous.put(valueType, r);
+                        }
+                        resolved &= r;
+                    }
                 }
             }
         }

Modified: sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -50,6 +50,7 @@ public final strictfp class DefaultAssoc
      *
      * @param cyclic {@code true} if in addition to the association from <var>A</var> to <var>B</var>,
      *        we also want an association from <var>B</var> to <var>A</var>, thus creating a cycle.
+     * @return The association to use for testing purpose.
      */
     static DefaultAssociationRole twinTown(final boolean cyclic) {
         final Map<String,?> properties = singletonMap(NAME_KEY, "twin town");
@@ -67,6 +68,7 @@ public final strictfp class DefaultAssoc
      *
      * @param cyclic {@code true} if in addition to the association from <var>A</var> to <var>B</var>,
      *        we also want an association from <var>B</var> to <var>A</var>, thus creating a cycle.
+     * @return The association to use for testing purpose.
      */
     static DefaultFeatureType twinTownCity(final boolean cyclic) {
         final DefaultAssociationRole twinTown = twinTown(cyclic);
@@ -80,6 +82,7 @@ public final strictfp class DefaultAssoc
      * @param name     The name as either a {@link String} or a {@link GenericName}.
      * @param parent   A feature type created by {@link DefaultFeatureTypeTest#city()}, or {@code null}.
      * @param property The association to an other feature.
+     * @return The feature type to use for testing purpose.
      */
     private static DefaultFeatureType createType(final Object name,
             final FeatureType parent, final FeatureAssociationRole... property)
@@ -147,29 +150,50 @@ public final strictfp class DefaultAssoc
         final GenericName nameOfB = DefaultFactories.SIS_NAMES.createTypeName(null, "B");
         final GenericName nameOfC = DefaultFactories.SIS_NAMES.createTypeName(null, "C");
         final GenericName nameOfD = DefaultFactories.SIS_NAMES.createTypeName(null, "D");
-
+        /*
+         * Associations defined only by the FeatureType name.
+         */
         final DefaultAssociationRole toB = new DefaultAssociationRole(singletonMap(NAME_KEY, "toB"), nameOfB, 1, 1);
         final DefaultAssociationRole toC = new DefaultAssociationRole(singletonMap(NAME_KEY, "toC"), nameOfC, 1, 1);
         final DefaultAssociationRole toD = new DefaultAssociationRole(singletonMap(NAME_KEY, "toD"), nameOfD, 1, 1);
-        final DefaultFeatureType   typeA = createType(nameOfA, null, toB);
-        final DefaultFeatureType   typeB = createType(nameOfB, null, toC);
-        final DefaultFeatureType   typeC = createType(nameOfC, null, toD);
-
-        final DefaultAssociationRole toA = new DefaultAssociationRole(singletonMap(NAME_KEY, "toA"), typeA, 1, 1);
-        final DefaultFeatureType typeD = createType(nameOfD, null, toA, toB, toC, toD);
-
+        final DefaultFeatureType typeA = createType(nameOfA, null, toB);
+        final DefaultFeatureType typeB = createType(nameOfB, null, toC);
+        final DefaultFeatureType typeC = createType(nameOfC, null, toD);
+        /*
+         * Association defined with real FeatureType instance, except for an association to itself.
+         * Construction of this FeatureType shall cause the resolution of all above FeatureTypes.
+         */
+        final DefaultAssociationRole toAr = new DefaultAssociationRole(singletonMap(NAME_KEY, "toA"),         typeA, 1, 1);
+        final DefaultAssociationRole toBr = new DefaultAssociationRole(singletonMap(NAME_KEY, toB.getName()), typeB, 1, 1);
+        final DefaultAssociationRole toCr = new DefaultAssociationRole(singletonMap(NAME_KEY, toC.getName()), typeC, 1, 1);
+        final DefaultFeatureType typeD = createType(nameOfD, null, toAr, toBr, toCr, toD);
+        /*
+         * Verify the property given to the constructors. There is no reason for those properties
+         * to change as they are not the instances to be replaced by the name resolutions, but we
+         * verify them as a paranoiac check.
+         */
         assertSame("A.properties", toB, getSingleton(typeA.getProperties(false)));
         assertSame("B.properties", toC, getSingleton(typeB.getProperties(false)));
         assertSame("C.properties", toD, getSingleton(typeC.getProperties(false)));
-        assertSame("D.properties", toA, typeD.getProperty("toA"));
-        assertSame("D.properties", toB, typeD.getProperty("toB"));
-        assertSame("D.properties", toC, typeD.getProperty("toC"));
-        assertSame("D.properties", toD, typeD.getProperty("toD"));
-        assertSame("toA", typeA, toA.getValueType());
-//      assertSame("toB", typeB, toB.getValueType());
-//      assertSame("toC", typeC, toC.getValueType());
-        assertSame("toD", typeD, toD.getValueType());
-
+        assertSame("D.properties", toAr, typeD.getProperty("toA"));
+        assertSame("D.properties", toBr, typeD.getProperty("toB"));
+        assertSame("D.properties", toCr, typeD.getProperty("toC"));
+        assertSame("D.properties", toD,  typeD.getProperty("toD"));
+        /*
+         * CORE OF THIS TEST: verify that the values of toB, toC and toD have been replaced by the actual
+         * FeatureType instances. Also verify that as a result, toB.equals(toBr) and toC.equals(toCr).
+         */
+        assertSame("toA", typeA, toAr.getValueType());
+        assertSame("toB", typeB, toBr.getValueType());
+        assertSame("toB", typeB, toB .getValueType());
+        assertSame("toC", typeC, toCr.getValueType());
+        assertSame("toC", typeC, toC .getValueType());
+        assertSame("toD", typeD, toD .getValueType());
+        assertEquals("toB", toB, toBr);
+        assertEquals("toC", toC, toCr);
+        /*
+         * Other equality tests, mostly for verifying that we do not fall in an infinite loop here.
+         */
         assertFalse("equals", typeA.equals(typeD));
         assertFalse("equals", typeD.equals(typeA));
         assertFalse("equals", typeB.equals(typeC));

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -171,9 +171,9 @@ public abstract class LegacyPropertyAdap
     /**
      * Emit a warning about extraneous ignored values.
      *
-     * @param  valueClass    The value class, used in case of warning only.
-     * @param  callerClass   The caller class, used in case of warning only.
-     * @param  callerMethod  The caller method, used in case of warning only.
+     * @param  valueClass    The value class (usually a GeoAPI interface).
+     * @param  callerClass   The caller class (usually an Apache SIS implementation of a GeoAPI interface).
+     * @param  callerMethod  The caller method (usually the name of a getter method).
      */
     public static void warnIgnoredExtraneous(final Class<?> valueClass,
             final Class<?> callerClass, final String callerMethod)

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -34,21 +34,11 @@ import static org.apache.sis.metadata.is
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.4
+ * @version 0.5
  * @module
  */
 public final class MetadataUtilities extends Static {
     /**
-     * The metadata standard name for ISO 19115-2.
-     */
-    public static final String STANDARD_NAME_2 = "ISO 19115-2 Geographic Information - Metadata Part 2 Extensions for imagery and gridded data";
-
-    /**
-     * The metadata standard version number for ISO 19115-2.
-     */
-    public static final String STANDARD_VERSION_2 = "ISO 19115-2:2009(E)";
-
-    /**
      * Do not allow instantiation of this class.
      */
     private MetadataUtilities() {

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -41,6 +41,10 @@ import org.apache.sis.util.CharSequences
  * @module
  */
 public final class Citations extends Static {
+    /*
+     * NOTE: other constants are defined in org.apache.sis.internal.metadata.Standards.
+     */
+
     /**
      * The <a href="http://www.iso.org/">International Organization for Standardization</a>.
      *
@@ -161,7 +165,7 @@ public final class Citations extends Sta
     /**
      * List of citations declared in this class.
      */
-    private static final Citation[] AUTHORITIES = {
+    private static final Citation[] CITATIONS = {
         ISO, OGC, OGP, SIS, ESRI, ORACLE, NETCDF, GEOTIFF, PROJ4, EPSG, ISBN, ISSN
     };
 
@@ -189,7 +193,7 @@ public final class Citations extends Sta
         if (title == null || ((title = CharSequences.trimWhitespaces(title)).isEmpty())) {
             return null;
         }
-        for (final Citation citation : AUTHORITIES) {
+        for (final Citation citation : CITATIONS) {
             if (titleMatches(citation, title)) {
                 return citation;
             }

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -31,6 +31,7 @@ import org.opengis.metadata.citation.Res
 import org.opengis.metadata.citation.Role;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.Types;
+import org.apache.sis.internal.metadata.LegacyPropertyAdapter;
 
 
 /**
@@ -116,26 +117,60 @@ public class DefaultResponsibleParty ext
     }
 
     /**
-     * Returns the name of the first party of the given type, or {@code null} if none.
+     * Returns the name or the position of the first individual. If no individual is found in the list of parties,
+     * then this method will search in the list of organization members. The later structure is used by our NetCDF
+     * reader.
+     *
+     * @param  position {@code true} for returning the position name instead than individual name.
+     * @return The name or position of the first individual, or {@code null}.
+     *
+     * @see #getIndividualName()
+     * @see #getPositionName()
      */
-    private InternationalString getName(final Class<? extends Party> type, final boolean position) {
+    private InternationalString getIndividual(final boolean position) {
         final Collection<Party> parties = getParties();
+        InternationalString name = getName(parties, Individual.class, position);
+        if (name == null && parties != null) {
+            for (final Party party : parties) {
+                if (party instanceof Organisation) {
+                    name = getName(((Organisation) party).getIndividual(), Individual.class, position);
+                    if (name != null) {
+                        break;
+                    }
+                }
+            }
+        }
+        return name;
+    }
+
+    /**
+     * Returns the name of the first party of the given type, or {@code null} if none.
+     *
+     * @param  position {@code true} for returning the position name instead than individual name.
+     * @return The name or position of the first individual, or {@code null}.
+     *
+     * @see #getOrganisationName()
+     * @see #getIndividualName()
+     * @see #getPositionName()
+     */
+    private static InternationalString getName(final Collection<? extends Party> parties,
+            final Class<? extends Party> type, final boolean position)
+    {
+        InternationalString name = null;
         if (parties != null) { // May be null on marshalling.
             for (final Party party : parties) {
                 if (type.isInstance(party)) {
-                    final InternationalString name;
-                    if (position) {
-                        name = ((Individual) party).getPositionName();
-                    } else {
-                        name = party.getName();
-                    }
                     if (name != null) {
-                        return name;
+                        LegacyPropertyAdapter.warnIgnoredExtraneous(type, DefaultResponsibleParty.class,
+                                position ? "getPositionName" : (type == Individual.class)
+                                         ? "getIndividualName" : "getOrganisationName");
+                        break;
                     }
+                    name = position ? ((Individual) party).getPositionName() : party.getName();
                 }
             }
         }
-        return null;
+        return name;
     }
 
     /**
@@ -168,8 +203,9 @@ public class DefaultResponsibleParty ext
      * Only one of {@code individualName}, {@link #getOrganisationName() organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation returns the first non-null name of an {@link Individual}
-     * in the collection of {@linkplain #getParties() parties}.</p>
+     * <p>This implementation returns the name of the first {@link Individual} found in the collection of
+     * {@linkplain #getParties() parties}. If no individual is found in the parties, then this method fallbacks
+     * on the first {@linkplain Organisation#getIndividual() organisation member}.</p>
      *
      * @return Name, surname, given name and title of the responsible person, or {@code null}.
      *
@@ -179,7 +215,7 @@ public class DefaultResponsibleParty ext
     @Deprecated
     @XmlElement(name = "individualName")
     public String getIndividualName() {
-        final InternationalString name = getName(Individual.class, false);
+        final InternationalString name = getIndividual(false);
         return (name != null) ? name.toString() : null;
     }
 
@@ -207,8 +243,8 @@ public class DefaultResponsibleParty ext
      * {@link #getIndividualName() individualName}, {@code organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation returns the first non-null name of an {@link Organisation}
-     * in the collection of {@linkplain #getParties() parties}.</p>
+     * <p>This implementation returns the name of the first {@link Organisation}
+     * found in the collection of {@linkplain #getParties() parties}.</p>
      *
      * @return Name of the responsible organization, or {@code null}.
      *
@@ -218,7 +254,7 @@ public class DefaultResponsibleParty ext
     @Deprecated
     @XmlElement(name = "organisationName")
     public InternationalString getOrganisationName() {
-        return getName(Organisation.class, false);
+        return getName(getParties(), Organisation.class, false);
     }
 
     /**
@@ -245,8 +281,9 @@ public class DefaultResponsibleParty ext
      * {@link #getIndividualName() individualName}, {@link #getOrganisationName() organisationName}
      * and {@code positionName} shall be provided.
      *
-     * <p>This implementation returns the first non-null position name of an {@link Individual}
-     * in the collection of {@linkplain #getParties() parties}.</p>
+     * <p>This implementation returns the position of the first {@link Individual} found in the collection of
+     * {@linkplain #getParties() parties}. If no individual is found in the parties, then this method fallbacks
+     * on the first {@linkplain Organisation#getIndividual() organisation member}.</p>
      *
      * @return Role or position of the responsible person, or {@code null}
      *
@@ -256,7 +293,7 @@ public class DefaultResponsibleParty ext
     @Deprecated
     @XmlElement(name = "positionName")
     public InternationalString getPositionName() {
-        return getName(DefaultIndividual.class, true);
+        return getIndividual(true);
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -249,7 +249,7 @@ public class DefaultCoverageDescription 
 
     /**
      * Sets the type of information represented by the cell value.
-     * This method stores the value in the {@linkplain #getAttributeGroups() attribute groups}.
+     * This method stores the value in the first writable {@linkplain #getAttributeGroups() attribute groups}.
      *
      * @param newValue The new content type.
      *
@@ -273,7 +273,7 @@ public class DefaultCoverageDescription 
 
     /**
      * Returns the information on the dimensions of the cell measurement value.
-     * This method fetches the values from the {@linkplain #getAttributeGroups() attribute groups}.
+     * This method fetches the values from the first {@linkplain #getAttributeGroups() attribute groups}.
      *
      * @return Dimensions of the cell measurement value.
      *

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Names.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Names.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Names.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Names.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -182,7 +182,7 @@ public final class Names extends Static 
      * @param  namespace The namespace, or {@code null} for the global namespace.
      * @param  separator The separator between the namespace and the local part.
      * @param  localPart The name which is locale in the given namespace.
-     * @return A local name in the given namespace.
+     * @return A type name in the given namespace.
      */
     public static TypeName createTypeName(final CharSequence namespace, final String separator, final CharSequence localPart) {
         ensureNonNull("localPart", localPart);

Modified: sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.HashMap;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Iterator;
 import java.io.IOException;
 import javax.measure.unit.Unit;
 import javax.measure.unit.SI;
@@ -47,9 +48,9 @@ import org.opengis.metadata.constraint.R
 import org.opengis.referencing.crs.VerticalCRS;
 
 import org.apache.sis.util.iso.Types;
-import org.apache.sis.util.iso.DefaultNameSpace;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.metadata.iso.DefaultMetadataScope;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.metadata.iso.extent.*;
 import org.apache.sis.metadata.iso.spatial.*;
@@ -65,7 +66,7 @@ import org.apache.sis.internal.netcdf.De
 import org.apache.sis.internal.netcdf.Variable;
 import org.apache.sis.internal.netcdf.GridGeometry;
 import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.internal.metadata.MetadataUtilities;
+import org.apache.sis.internal.metadata.Standards;
 
 // The following dependency is used only for static final String constants.
 // Consequently the compiled class files should not have this dependency.
@@ -115,6 +116,11 @@ final class MetadataReader {
     private static final String[] SEARCH_PATH = {"NCISOMetadata", "CFMetadata", null, "THREDDSMetadata"};
 
     /**
+     * Names of global attributes identifying services.
+     */
+    private static final String[] SERVICES = {"wms_service", "wcs_service"};
+
+    /**
      * The string to use as a keyword separator. This separator is used for parsing the
      * {@value org.apache.sis.metadata.netcdf.AttributeNames#KEYWORDS} attribute value.
      * This is a regular expression.
@@ -164,7 +170,7 @@ final class MetadataReader {
      * An object very similar is used as the creator. The point of contact and the creator
      * are often identical except for their role attribute.
      */
-    private transient ResponsibleParty pointOfContact;
+    private transient Responsibility pointOfContact;
 
     /**
      * Creates a new <cite>NetCDF to ISO</cite> mapper for the given source.
@@ -186,6 +192,14 @@ final class MetadataReader {
     }
 
     /**
+     * Returns the first element of the given collection.
+     */
+    private static <T> T first(final Collection<T> collection) {
+        final Iterator<T> it = collection.iterator();
+        return it.hasNext() ? it.next() : null;
+    }
+
+    /**
      * Adds the given element in the given collection if the element is not already present in the collection.
      * We define this method because the metadata API uses collections while the SIS implementation uses lists.
      * The lists are usually very short (typically 0 or 1 element), so the call to {@link List#contains(Object)}
@@ -220,7 +234,7 @@ final class MetadataReader {
      * @param metadata  The value stored in the metadata object.
      * @param attribute The value parsed from the NetCDF file.
      */
-    private static boolean isDefined(final CharSequence metadata, final String attribute) {
+    private static boolean canShare(final CharSequence metadata, final String attribute) {
         return (attribute == null) || (metadata != null && metadata.toString().equals(attribute));
     }
 
@@ -231,7 +245,7 @@ final class MetadataReader {
      * @param metadata  The value stored in the metadata object.
      * @param attribute The value parsed from the NetCDF file.
      */
-    private static boolean isDefined(final Collection<String> metadata, final String attribute) {
+    private static boolean canShare(final Collection<String> metadata, final String attribute) {
         return (attribute == null) || metadata.contains(attribute);
     }
 
@@ -241,8 +255,8 @@ final class MetadataReader {
      * @param resource  The value stored in the metadata object.
      * @param url       The value parsed from the NetCDF file.
      */
-    private static boolean isDefined(final OnlineResource resource, final String url) {
-        return (url == null) || (resource != null && isDefined(resource.getLinkage().toString(), url));
+    private static boolean canShare(final OnlineResource resource, final String url) {
+        return (url == null) || (resource != null && canShare(resource.getLinkage().toString(), url));
     }
 
     /**
@@ -251,8 +265,8 @@ final class MetadataReader {
      * @param address  The value stored in the metadata object.
      * @param email    The value parsed from the NetCDF file.
      */
-    private static boolean isDefined(final Address address, final String email) {
-        return (email == null) || (address != null && isDefined(address.getElectronicMailAddresses(), email));
+    private static boolean canShare(final Address address, final String email) {
+        return (email == null) || (address != null && canShare(address.getElectronicMailAddresses(), email));
     }
 
     /**
@@ -302,31 +316,7 @@ final class MetadataReader {
     }
 
     /**
-     * Returns a globally unique identifier for the current NetCDF {@linkplain #decoder}.
-     * The default implementation builds the identifier from the following attributes:
-     *
-     * <ul>
-     *   <li>{@value #NAMING_AUTHORITY} used as the {@linkplain Identifier#getAuthority() authority}.</li>
-     *   <li>{@value #IDENTIFIER}, or {@link ucar.nc2.NetcdfFile#getId()} if no identifier attribute was found.</li>
-     * </ul>
-     *
-     * @return The globally unique identifier, or {@code null} if none.
-     * @throws IOException If an I/O operation was necessary but failed.
-     */
-    private Identifier getFileIdentifier() throws IOException {
-        String identifier = decoder.stringValue(IDENTIFIER);
-        if (identifier == null) {
-            identifier = decoder.getId();
-            if (identifier == null) {
-                return null;
-            }
-        }
-        final String namespace  = decoder.stringValue(NAMING_AUTHORITY);
-        return new DefaultIdentifier((namespace != null) ? new DefaultCitation(namespace) : null, identifier);
-    }
-
-    /**
-     * Creates a {@code ResponsibleParty} element if at least one of the name, email or URL attributes is defined.
+     * Creates a {@code Responsibility} element if at least one of the name, email or URL attributes is defined.
      * For more consistent results, the caller should restrict the {@linkplain Decoder#setSearchPath search path}
      * to a single group before invoking this method.
      *
@@ -342,7 +332,7 @@ final class MetadataReader {
      * @see AttributeNames#CONTRIBUTOR
      * @see AttributeNames#PUBLISHER
      */
-    private ResponsibleParty createResponsibleParty(final Responsible keys, final boolean isPointOfContact)
+    private Responsibility createResponsibleParty(final Responsible keys, final boolean isPointOfContact)
             throws IOException
     {
         final String individualName   = decoder.stringValue(keys.NAME);
@@ -356,49 +346,67 @@ final class MetadataReader {
         if (role == null) {
             role = isPointOfContact ? Role.POINT_OF_CONTACT : keys.DEFAULT_ROLE;
         }
-        ResponsibleParty party    = pointOfContact;
-        Contact          contact  = null;
-        Address          address  = null;
-        OnlineResource   resource = null;
-        if (party != null) {
-            contact = party.getContactInfo();
-            if (contact != null) {
-                address  = contact.getAddress();
-                resource = contact.getOnlineResource();
-            }
-            if (!isDefined(resource, url)) {
-                resource = null;
-                contact  = null; // Clear the parents all the way up to the root.
-                party    = null;
-            }
-            if (!isDefined(address, email)) {
-                address = null;
-                contact = null; // Clear the parents all the way up to the root.
-                party   = null;
-            }
+        /*
+         * Verify if we can share the existing 'pointOfContact' instance. This is often the case in practice.
+         * If we can not share the whole existing instance, we usually can share parts of it like the address.
+         */
+        Responsibility responsibility = pointOfContact;
+        Contact        contact        = null;
+        Address        address        = null;
+        OnlineResource resource       = null;
+        if (responsibility != null) {
+            final Party party = first(responsibility.getParties());
             if (party != null) {
-                if (!isDefined(party.getOrganisationName(), organisationName) ||
-                    !isDefined(party.getIndividualName(),   individualName))
-                {
-                    party = null;
+                contact = first(party.getContactInfo());
+                if (contact != null) {
+                    address  = first(contact.getAddresses());
+                    resource = first(contact.getOnlineResources());
+                }
+                if (!canShare(resource, url)) {
+                    resource       = null;
+                    contact        = null; // Clear the parents all the way up to the root.
+                    responsibility = null;
+                }
+                if (!canShare(address, email)) {
+                    address        = null;
+                    contact        = null; // Clear the parents all the way up to the root.
+                    responsibility = null;
+                }
+                if (responsibility != null) {
+                    if (party instanceof Organisation) {
+                        // Individual (if any) is considered an organisation member. See comment in next block.
+                        if (!canShare(party.getName(), organisationName) ||
+                            !canShare(first(((Organisation) party).getIndividual()).getName(), individualName))
+                        {
+                            responsibility = null;
+                        }
+                    } else if (!canShare(party.getName(), individualName)) {
+                        responsibility = null;
+                    }
                 }
             }
         }
-        if (party == null) {
+        /*
+         * If we can not share the exiting instance, we have to build a new one. If there is both
+         * an individual and organisation name, then the individual is considered a member of the
+         * organisation. This structure shall be kept consistent with the check in the above block.
+         */
+        if (responsibility == null) {
             if (contact == null) {
                 if (address  == null) address  = createAddress(email);
                 if (resource == null) resource = createOnlineResource(url);
                 contact = createContact(address, resource);
             }
             if (individualName != null || organisationName != null || contact != null) { // Do not test role.
-                final DefaultResponsibleParty np = new DefaultResponsibleParty(role);
-                np.setIndividualName(individualName);
-                np.setOrganisationName(toInternationalString(organisationName));
-                np.setContactInfo(contact);
-                party = np;
+                AbstractParty party = null;
+                if (individualName   != null) party = new DefaultIndividual(individualName, null, null);
+                if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (Individual) party, null);
+                if (party            == null) party = new AbstractParty(); // We don't know if this is an individual or an organisation.
+                if (contact          != null) party.setContactInfo(singleton(contact));
+                responsibility = new DefaultResponsibility(role, null, party);
             }
         }
-        return party;
+        return responsibility;
     }
 
     /**
@@ -432,15 +440,13 @@ final class MetadataReader {
         if (issued   != null) citation.getDates()  .add  (new DefaultCitationDate(issued,   DateType.PUBLICATION));
         if (pointOfContact != null) {
             // Same responsible party than the contact, except for the role.
-            final DefaultResponsibleParty np = new DefaultResponsibleParty(Role.ORIGINATOR);
-            np.setIndividualName  (pointOfContact.getIndividualName());
-            np.setOrganisationName(pointOfContact.getOrganisationName());
-            np.setContactInfo     (pointOfContact.getContactInfo());
+            final DefaultResponsibility np = new DefaultResponsibility(pointOfContact);
+            np.setRole(Role.ORIGINATOR);
             citation.setCitedResponsibleParties(singleton(np));
         }
         for (final String path : searchPath) {
             decoder.setSearchPath(path);
-            final ResponsibleParty contributor = createResponsibleParty(CONTRIBUTOR, false);
+            final Responsibility contributor = createResponsibleParty(CONTRIBUTOR, false);
             if (contributor != null && contributor != pointOfContact) {
                 addIfAbsent(citation.getCitedResponsibleParties(), contributor);
             }
@@ -733,6 +739,7 @@ final class MetadataReader {
             if (!variable.isCoverage(2)) {
                 continue;
             }
+            DefaultAttributeGroup group = null;
             /*
              * Instantiate a CoverageDescription for each distinct set of NetCDF dimensions
              * (e.g. longitude,latitude,time). This separation is based on the fact that a
@@ -752,8 +759,14 @@ final class MetadataReader {
                     content = new DefaultCoverageDescription();
                 }
                 contents.put(dimensions, content);
+            } else {
+                group = (DefaultAttributeGroup) first(content.getAttributeGroups());
+            }
+            if (group == null) {
+                group = new DefaultAttributeGroup();
+                content.setAttributeGroups(singleton(group));
             }
-            content.getDimensions().add(createSampleDimension(variable));
+            group.getAttributes().add(createSampleDimension(variable));
             final Object[] names    = variable.getAttributeValues(FLAG_NAMES,    false);
             final Object[] meanings = variable.getAttributeValues(FLAG_MEANINGS, false);
             final Object[] masks    = variable.getAttributeValues(FLAG_MASKS,    true);
@@ -792,7 +805,7 @@ final class MetadataReader {
         }
         String description = variable.getDescription();
         if (description != null && !(description = description.trim()).isEmpty() && !description.equals(name)) {
-            band.setDescriptor(toInternationalString(description));
+            band.setDescription(toInternationalString(description));
         }
 //TODO: Can't store the units, because the Band interface restricts it to length.
 //      We need the SampleDimension interface proposed in ISO 19115 revision draft.
@@ -829,42 +842,60 @@ final class MetadataReader {
     }
 
     /**
+     * Returns a globally unique identifier for the current NetCDF {@linkplain #decoder}.
+     * The default implementation builds the identifier from the following attributes:
+     *
+     * <ul>
+     *   <li>{@value #NAMING_AUTHORITY} used as the {@linkplain Identifier#getAuthority() authority}.</li>
+     *   <li>{@value #IDENTIFIER}, or {@link ucar.nc2.NetcdfFile#getId()} if no identifier attribute was found.</li>
+     * </ul>
+     *
+     * @return The globally unique identifier, or {@code null} if none.
+     * @throws IOException If an I/O operation was necessary but failed.
+     */
+    private Identifier getFileIdentifier() throws IOException {
+        String identifier = decoder.stringValue(IDENTIFIER);
+        if (identifier == null) {
+            identifier = decoder.getId();
+            if (identifier == null) {
+                return null;
+            }
+        }
+        final String namespace  = decoder.stringValue(NAMING_AUTHORITY);
+        return new DefaultIdentifier((namespace != null) ? new DefaultCitation(namespace) : null, identifier);
+    }
+
+    /**
      * Creates an ISO {@code Metadata} object from the information found in the NetCDF file.
-     * The returned metadata will be unmodifiable in order to allow the caller to cache it.
      *
      * @return The ISO metadata object.
      * @throws IOException If an I/O operation was necessary but failed.
      */
     public Metadata read() throws IOException {
         final DefaultMetadata metadata = new DefaultMetadata();
-        metadata.setMetadataStandardName(MetadataUtilities.STANDARD_NAME_2);
-        metadata.setMetadataStandardVersion(MetadataUtilities.STANDARD_VERSION_2);
+        metadata.setMetadataStandards(singleton(Standards.ISO_19115_2));
         final Identifier identifier = getFileIdentifier();
-        if (identifier != null) {
-            String code = identifier.getCode();
-            final Citation authority = identifier.getAuthority();
-            if (authority != null) {
-                final InternationalString title = authority.getTitle();
-                if (title != null) {
-                    code = title.toString() + DefaultNameSpace.DEFAULT_SEPARATOR + code;
-                }
+        metadata.setMetadataIdentifier(identifier);
+        final Date creation = decoder.dateValue(METADATA_CREATION);
+        if (creation != null) {
+            metadata.setDates(singleton(new DefaultCitationDate(creation, DateType.CREATION)));
+        }
+        metadata.setMetadataScopes(singleton(new DefaultMetadataScope(ScopeCode.DATASET)));
+        for (final String service : SERVICES) {
+            final String name = decoder.stringValue(service);
+            if (name != null) {
+                final DefaultMetadataScope scope = new DefaultMetadataScope(ScopeCode.SERVICE);
+                scope.setName(new SimpleInternationalString(name));
+                metadata.getMetadataScopes().add(scope);
             }
-            metadata.setFileIdentifier(code);
-        }
-        metadata.setDateStamp(decoder.dateValue(METADATA_CREATION));
-        metadata.setHierarchyLevels(singleton(ScopeCode.DATASET));
-        final String wms = decoder.stringValue("wms_service");
-        final String wcs = decoder.stringValue("wcs_service");
-        if (wms != null || wcs != null) {
-            metadata.getHierarchyLevels().add(ScopeCode.SERVICE);
         }
         /*
-         * Add the ResponsibleParty which is declared in global attributes, or in
+         * Add the responsible party which is declared in global attributes, or in
          * the THREDDS attributes if no information was found in global attributes.
          */
         for (final String path : searchPath) {
             decoder.setSearchPath(path);
-            final ResponsibleParty party = createResponsibleParty(CREATOR, true);
+            final Responsibility party = createResponsibleParty(CREATOR, true);
             if (party != null && party != pointOfContact) {
                 addIfAbsent(metadata.getContacts(), party);
                 if (pointOfContact == null) {
@@ -880,17 +911,19 @@ final class MetadataReader {
         DefaultDistribution distribution   = null;
         for (final String path : searchPath) {
             decoder.setSearchPath(path);
-            final ResponsibleParty party = createResponsibleParty(PUBLISHER, false);
-            if (party != null) {
+            final Responsibility r = createResponsibleParty(PUBLISHER, false);
+            if (r != null) {
                 if (distribution == null) {
                     distribution = new DefaultDistribution();
                     metadata.setDistributionInfo(singleton(distribution));
                 }
-                final DefaultDistributor distributor = new DefaultDistributor(party);
+                final DefaultDistributor distributor = new DefaultDistributor(r);
                 // TODO: There is some transfert option, etc. that we could set there.
                 // See UnidataDD2MI.xsl for options for OPeNDAP, THREDDS, etc.
                 addIfAbsent(distribution.getDistributors(), distributor);
-                publisher = addIfNonNull(publisher, toInternationalString(party.getIndividualName()));
+                for (final Party party : r.getParties()) {
+                    publisher = addIfNonNull(publisher, party.getName());
+                }
             }
             // Also add history.
             final String history = decoder.stringValue(HISTORY);
@@ -920,7 +953,6 @@ final class MetadataReader {
                 metadata.getSpatialRepresentationInfo().add(createSpatialRepresentationInfo(cs));
             }
         }
-        metadata.freeze();
         return metadata;
     }
 }

Modified: sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -24,6 +24,7 @@ import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.internal.netcdf.Decoder;
+import org.apache.sis.metadata.ModifiableMetadata;
 
 
 /**
@@ -78,7 +79,10 @@ public class NetcdfStore extends DataSto
     public Metadata getMetadata() throws DataStoreException {
         if (metadata == null) try {
             final MetadataReader reader = new MetadataReader(decoder);
-            metadata = reader.read(); // Umodifiable object.
+            metadata = reader.read();
+            if (metadata instanceof ModifiableMetadata) {
+                ((ModifiableMetadata) metadata).freeze();
+            }
         } catch (IOException e) {
             throw new DataStoreException(e);
         }

Modified: sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/ConformanceTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/ConformanceTest.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/ConformanceTest.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/ConformanceTest.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -88,7 +88,7 @@ public final strictfp class ConformanceT
      * @param hasContact {@code true} for adding contact information.
      */
     private static void addCommonProperties(final Map<String,Object> expected, final boolean hasContact) {
-        assertNull(expected.put("metadataStandardName", "ISO 19115-2 Geographic Information - Metadata Part 2 Extensions for imagery and gridded data"));
+        assertNull(expected.put("metadataStandardName", "ISO 19115-2 Geographic Information — Metadata Part 2: Extensions for imagery and gridded data"));
         assertNull(expected.put("metadataStandardVersion", "ISO 19115-2:2009(E)"));
         if (hasContact) {
             assertNull(expected.put("identificationInfo.pointOfContact.role", Role.POINT_OF_CONTACT));

Modified: sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java?rev=1633643&r1=1633642&r2=1633643&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java [UTF-8] Wed Oct 22 15:53:16 2014
@@ -85,9 +85,9 @@ public final strictfp class MetadataRead
         assertMultilinesEquals(
             "Metadata\n" +
             "  ├─Contact\n" +
-            "  │   ├─Role…………………………………………………………………………………………… Point of contact\n" +
-            "  │   └─Party\n" +
-            "  │       └─Name………………………………………………………………………………… NOAA/NWS/NCEP\n" +
+            "  │   ├─Party\n" +
+            "  │   │   └─Name………………………………………………………………………………… NOAA/NWS/NCEP\n" +
+            "  │   └─Role…………………………………………………………………………………………… Point of contact\n" +
             "  ├─Spatial representation info\n" +
             "  │   ├─Number of dimensions………………………………………………… 3\n" +
             "  │   ├─Axis dimension properties (1 of 3)\n" +
@@ -112,14 +112,14 @@ public final strictfp class MetadataRead
             "  │   │   │   └─Authority\n" +
             "  │   │   │       └─Title………………………………………………………… edu.ucar.unidata\n" +
             "  │   │   └─Cited responsible party\n" +
-            "  │   │       ├─Role……………………………………………………………………… Originator\n" +
-            "  │   │       └─Party\n" +
-            "  │   │           └─Name…………………………………………………………… NOAA/NWS/NCEP\n" +
+            "  │   │       ├─Party\n" +
+            "  │   │       │   └─Name…………………………………………………………… NOAA/NWS/NCEP\n" +
+            "  │   │       └─Role……………………………………………………………………… Originator\n" +
             "  │   ├─Abstract………………………………………………………………………………… NCEP SST Global 5.0 x 2.5 degree model data\n" +
             "  │   ├─Point of contact\n" +
-            "  │   │   ├─Role………………………………………………………………………………… Point of contact\n" +
-            "  │   │   └─Party\n" +
-            "  │   │       └─Name……………………………………………………………………… NOAA/NWS/NCEP\n" +
+            "  │   │   ├─Party\n" +
+            "  │   │   │   └─Name……………………………………………………………………… NOAA/NWS/NCEP\n" +
+            "  │   │   └─Role………………………………………………………………………………… Point of contact\n" +
             "  │   ├─Descriptive keywords\n" +
             "  │   │   ├─Keyword………………………………………………………………………… EARTH SCIENCE > Oceans > Ocean Temperature > Sea Surface Temperature\n" +
             "  │   │   ├─Type………………………………………………………………………………… Theme\n" +
@@ -150,9 +150,11 @@ public final strictfp class MetadataRead
             "  ├─Metadata scope\n" +
             "  │   └─Resource scope………………………………………………………………… Dataset\n" +
             "  ├─Metadata identifier\n" +
-            "  │   └─Code…………………………………………………………………………………………… edu.ucar.unidata:NCEP/SST/Global_5x2p5deg/SST_Global_5x2p5deg_20050922_0000.nc\n" +
+            "  │   ├─Code…………………………………………………………………………………………… NCEP/SST/Global_5x2p5deg/SST_Global_5x2p5deg_20050922_0000.nc\n" +
+            "  │   └─Authority\n" +
+            "  │       └─Title……………………………………………………………………………… edu.ucar.unidata\n" +
             "  └─Metadata standard\n" +
-            "      ├─Title………………………………………………………………………………………… ISO 19115-2 Geographic Information - Metadata Part 2 Extensions for imagery and gridded data\n" +
+            "      ├─Title………………………………………………………………………………………… ISO 19115-2 Geographic Information — Metadata Part 2: Extensions for imagery and gridded data\n" +
             "      └─Edition…………………………………………………………………………………… ISO 19115-2:2009(E)\n", text);
     }
 }



Mime
View raw message