sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1634099 [1/3] - in /sis/branches/JDK6: ./ application/sis-javafx/ core/sis-build-helper/src/main/java/org/apache/sis/util/resources/ core/sis-build-helper/src/test/ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/sr...
Date Fri, 24 Oct 2014 17:28:30 GMT
Author: desruisseaux
Date: Fri Oct 24 17:28:28 2014
New Revision: 1634099

URL: http://svn.apache.org/r1634099
Log:
Merge from the JDK7 branch.

Added:
    sis/branches/JDK6/core/sis-build-helper/src/test/
      - copied from r1634098, sis/branches/JDK7/core/sis-build-helper/src/test/
    sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
      - copied unchanged from r1634098, sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Standards.java
      - copied unchanged from r1634098, sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Standards.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
      - copied unchanged from r1634098, sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/internal/
      - copied from r1634098, sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/internal/
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/Database.java
      - copied, changed from r1634098, sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/Database.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/package-info.java
      - copied unchanged from r1634098, sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/package-info.java
    sis/branches/JDK6/storage/sis-shapefile/src/test/java/org/apache/sis/internal/
      - copied from r1634098, sis/branches/JDK7/storage/sis-shapefile/src/test/java/org/apache/sis/internal/
Removed:
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/CmdLineDriver.java
    sis/branches/JDK6/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/CmdLineDriverTest.java
Modified:
    sis/branches/JDK6/   (props changed)
    sis/branches/JDK6/application/sis-javafx/pom.xml
    sis/branches/JDK6/core/sis-build-helper/src/main/java/org/apache/sis/util/resources/ResourceCompilerMojo.java
    sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
    sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
    sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
    sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
    sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
    sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
    sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
    sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
    sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
    sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/code/CodeListMarshallingTest.java
    sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/metadata/PrunerTest.java
    sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
    sis/branches/JDK6/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
    sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/test/integration/DefaultMetadataTest.java
    sis/branches/JDK6/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ReferencingInMetadataTest.java
    sis/branches/JDK6/core/sis-referencing/src/test/resources/org/apache/sis/test/integration/Metadata.xml
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Version.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/iso/Names.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/internal/jaxb/gmd/LanguageCodeTest.java
    sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java
    sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/test/mock/MetadataMock.java
    sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/xml/NilReasonTest.java
    sis/branches/JDK6/ide-project/NetBeans/build.xml
    sis/branches/JDK6/ide-project/NetBeans/nbproject/project.properties
    sis/branches/JDK6/pom.xml
    sis/branches/JDK6/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
    sis/branches/JDK6/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
    sis/branches/JDK6/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/ConformanceTest.java
    sis/branches/JDK6/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
    sis/branches/JDK6/storage/sis-shapefile/pom.xml
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractConnection.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractResultSet.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractStatement.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBFDriver.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/CodePage.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/DataType.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/FieldDescriptor.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeFile.java
    sis/branches/JDK6/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeTypeEnum.java
    sis/branches/JDK6/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java
    sis/branches/JDK6/storage/sis-shapefile/src/test/java/org/apache/sis/test/suite/ShapefileTestSuite.java
    sis/branches/JDK6/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/XMLStoreTest.java

Propchange: sis/branches/JDK6/
------------------------------------------------------------------------------
  Merged /sis/branches/JDK8:r1631331-1634096
  Merged /sis/branches/JDK7:r1631333-1634098

Modified: sis/branches/JDK6/application/sis-javafx/pom.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/application/sis-javafx/pom.xml?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/application/sis-javafx/pom.xml (original)
+++ sis/branches/JDK6/application/sis-javafx/pom.xml Fri Oct 24 17:28:28 2014
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>0.5-jdk8-SNAPSHOT</version>
+    <version>0.5-jdk7-SNAPSHOT</version>
   </parent>
 
 

Modified: sis/branches/JDK6/core/sis-build-helper/src/main/java/org/apache/sis/util/resources/ResourceCompilerMojo.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-build-helper/src/main/java/org/apache/sis/util/resources/ResourceCompilerMojo.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-build-helper/src/main/java/org/apache/sis/util/resources/ResourceCompilerMojo.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-build-helper/src/main/java/org/apache/sis/util/resources/ResourceCompilerMojo.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -18,6 +18,7 @@ package org.apache.sis.util.resources;
 
 import java.io.File;
 import java.io.FilenameFilter;
+import java.util.Arrays;
 import java.util.List;
 
 import org.apache.maven.model.Resource;
@@ -27,6 +28,9 @@ import org.apache.maven.project.MavenPro
 import org.codehaus.plexus.util.Scanner;
 import org.sonatype.plexus.build.incremental.BuildContext;
 
+import static org.apache.sis.util.resources.IndexedResourceCompiler.JAVA_EXT;
+import static org.apache.sis.util.resources.IndexedResourceCompiler.PROPERTIES_EXT;
+
 
 /**
  * Compiles the international resources that are found in the module from which this mojo is invoked.
@@ -35,7 +39,7 @@ import org.sonatype.plexus.build.increme
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Olivier Nouguier (Geomatys)
  * @since   0.3 (derived from geotk-3.00)
- * @version 0.4
+ * @version 0.5
  * @module
  *
  * @goal compile-resources
@@ -96,6 +100,12 @@ public class ResourceCompilerMojo extend
     private File javaDirectoryFile;
 
     /**
+     * Constructs a new resource compiler MOJO.
+     */
+    public ResourceCompilerMojo() {
+    }
+
+    /**
      * Executes the mojo.
      *
      * @throws MojoExecutionException if the plugin execution failed.
@@ -146,8 +156,8 @@ public class ResourceCompilerMojo extend
     }
 
     /**
-     * Recursively scans the directories for a sub-package named "resources",
-     * then invokes the resource compiler for that directory.
+     * Recursively scans the directories and find all Java classes having a property files of the same name.
+     * Then invokes the resource compiler for those files.
      */
     private int processAllResourceDirectories(final File directory) throws ResourceCompilerException {
         int errors = 0;
@@ -155,14 +165,16 @@ public class ResourceCompilerMojo extend
         if (subdirs != null) { // Appears to be sometime null with auto-generated sub-directories.
             for (final File subdir : subdirs) {
                 if (subdir.isDirectory()) {
-                    if (subdir.getName().equals("resources")) {
-                        final File[] resourcesToProcess = subdir.listFiles(this);
-                        if (resourcesToProcess != null && resourcesToProcess.length != 0) {
+                    File[] resourcesToProcess = subdir.listFiles(this);
+                    int count = filterLanguages(resourcesToProcess);
+                    if (count != 0) {
+                        count = toJavaSourceFiles(resourcesToProcess, count);
+                        if (count != 0) {
+                            resourcesToProcess = Arrays.copyOf(resourcesToProcess, count);
                             errors += new Compiler(resourcesToProcess).run();
                         }
-                    } else {
-                        errors += processAllResourceDirectories(subdir);
                     }
+                    errors += processAllResourceDirectories(subdir);
                 }
             }
         }
@@ -170,22 +182,84 @@ public class ResourceCompilerMojo extend
     }
 
     /**
-     * Returns {@code true} if the given file is the source code for a resources bundle.
-     * This method returns {@code true} if the given file is a Java source file and if a
-     * properties file of the same name exists.
+     * Accepts all {@code "*.properties"} files.
      *
      * @param directory The directory.
      * @param name The file name.
      * @return {@code true} if the given file is a property file.
      */
     @Override
-    public final boolean accept(final File directory, String name) {
-        if (!name.endsWith(IndexedResourceCompiler.JAVA_EXT)) {
-            return false;
-        }
-        name = name.substring(0, name.length() - IndexedResourceCompiler.JAVA_EXT.length());
-        name += IndexedResourceCompiler.PROPERTIES_EXT;
-        return new File(directory, name).isFile();
+    public final boolean accept(final File directory, final String name) {
+        return name.endsWith(PROPERTIES_EXT);
+    }
+
+    /**
+     * Retains only the properties files which seems to be about internationalized resources.
+     * For example if the given array contains the following files:
+     * <ul>
+     *   <li>{@code "Errors.properties"}</li>
+     *   <li>{@code "Errors_en.properties"}</li>
+     *   <li>{@code "Errors_fr.properties"}</li>
+     *   <li>{@code "Messages.properties"}</li>
+     *   <li>{@code "Messages_en.properties"}</li>
+     *   <li>{@code "Messages_fr.properties"}</li>
+     *   <li>{@code "NotAnInternationalResource.properties"}</li>
+     * </ul>
+     *
+     * Then this method will retain the following files:
+     * <ul>
+     *   <li>{@code "Errors.properties"}</li>
+     *   <li>{@code "Messages.properties"}</li>
+     * </ul>
+     *
+     * @param  resourcesToProcess The files to filter. This array will be overwritten in-place.
+     * @return Number of valid elements in the {@code resourcesToProcess} after this method completion.
+     */
+    static int filterLanguages(final File[] resourcesToProcess) {
+        int count = 0;
+        if (resourcesToProcess != null) {
+            Arrays.sort(resourcesToProcess);
+            for (int i=0; i<resourcesToProcess.length;) {
+                final File file = resourcesToProcess[i];
+                String name = file.getName();
+                name = name.substring(0, name.length() - PROPERTIES_EXT.length()) + '_';
+                final int fileIndex = i;
+                while (++i < resourcesToProcess.length) {
+                    if (!resourcesToProcess[i].getName().startsWith(name)) {
+                        break;
+                    }
+                }
+                // Accepts the property file only if we found at least one language.
+                // Example: "Messages.properties" and "Messages_en.properties".
+                if (i - fileIndex >= 2) {
+                    resourcesToProcess[count++] = file;
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Converts the given property files into Java source file, provided that the later exists.
+     * The given array is overwritten in place.
+     *
+     * @param  resourcesToProcess The filtered resource files, as returned by {@link #filterLanguages(File[])}.
+     * @param  count Number of valid elements in {@code resourcesToProcess}.
+     * @return Number of valid elements after this method completion.
+     */
+    private static int toJavaSourceFiles(final File[] resourcesToProcess, final int count) {
+        int n = 0;
+        for (int i=0; i<count; i++) {
+            File file = resourcesToProcess[i];
+            String name = file.getName();
+            name = name.substring(0, name.length() - PROPERTIES_EXT.length());
+            name += JAVA_EXT;
+            file = new File(file.getParentFile(), name);
+            if (file.isFile()) {
+                resourcesToProcess[n++] = file;
+            }
+        }
+        return n;
     }
 
     /**

Modified: sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -223,7 +223,7 @@ public abstract class AbstractAssociatio
     public String toString() {
         final String pt = DefaultAssociationRole.getTitleProperty(role);
         final Iterator<Feature> it = getValues().iterator();
-        return FieldType.toString("FeatureAssociation", role, role.getValueType().getName(), new Iterator<Object>() {
+        return FieldType.toString("FeatureAssociation", role, DefaultAssociationRole.getValueTypeName(role), new Iterator<Object>() {
             @Override public boolean hasNext() {
                 return it.hasNext();
             }

Modified: sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -203,10 +203,17 @@ public class AbstractIdentifiedType impl
      * <p>For {@linkplain DefaultFeatureType feature types}, the name is mandatory and shall be unique
      * in the unit processing the data (e.g. a {@link org.apache.sis.storage.DataStore} reading a file).</p>
      *
+     * <div class="note"><b>Note for subclasses:</b>
+     * this method is final because it is invoked (indirectly) by subclass constructors,
+     * and invoking a user-overrideable method at construction time is not recommended.
+     * Furthermore, this attribute is often used as the primary key for {@code IdentifiedType} instances
+     * and need some guarantees about its stability.
+     * </div>
+     *
      * @return The type name.
      */
     @Override
-    public GenericName getName() {
+    public final GenericName getName() {
         return name;
     }
 

Modified: sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -17,8 +17,12 @@
 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;
 import org.apache.sis.util.Debug;
 
 import static org.apache.sis.util.ArgumentChecks.*;
@@ -65,7 +69,7 @@ public class DefaultAssociationRole exte
      *
      * @see #getValueType()
      */
-    private final FeatureType valueType;
+    private volatile FeatureType valueType;
 
     /**
      * The name of the property to use as a title for the associated feature, or an empty string if none.
@@ -77,7 +81,7 @@ public class DefaultAssociationRole exte
     private volatile transient String titleProperty;
 
     /**
-     * Constructs an association role from the given properties. The properties map is given unchanged to
+     * 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:
      *
@@ -125,13 +129,203 @@ public class DefaultAssociationRole exte
     }
 
     /**
+     * Constructs an association to a feature type of the given name.
+     * 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>
+     * 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.
+     * @param maximumOccurs  The maximum number of occurrences of the association within its containing entity,
+     *                       or {@link Integer#MAX_VALUE} if there is no restriction.
+     */
+    public DefaultAssociationRole(final Map<String,?> identification, final GenericName valueType,
+            final int minimumOccurs, final int maximumOccurs)
+    {
+        super(identification, minimumOccurs, maximumOccurs);
+        ensureNonNull("valueType", valueType);
+        this.valueType = new NamedFeatureType(valueType);
+    }
+
+    /**
+     * If the associated feature type is a placeholder for a {@code FeatureType} to be defined later,
+     * replaces the placeholder by the actual instance if available. Otherwise do nothing.
+     *
+     * This method is needed only in case of cyclic graph, e.g. feature <var>A</var> has an association
+     * to feature <var>B</var> which has an association back to <var>A</var>. It may also be <var>A</var>
+     * having an association to itself, <i>etc.</i>
+     *
+     * @param  creating The feature type in process of being constructed.
+     * @return {@code true} if this association references a resolved feature type after this method call.
+     */
+    final boolean resolve(final DefaultFeatureType creating) {
+        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<FeatureType>();
+                type = search(creating, name, deferred);
+                if (type == null) {
+                    /*
+                     * 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;
+        }
+        return true;
+    }
+
+    /**
+     * 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.
+     */
+    @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
+         * the request feature type in explicitly declared associations.
+         */
+        for (final PropertyType property : feature.getProperties(false)) {
+            if (property instanceof FeatureAssociationRole) {
+                final FeatureType valueType;
+                if (property instanceof DefaultAssociationRole) {
+                    valueType = ((DefaultAssociationRole) property).valueType;
+                    if (valueType instanceof NamedFeatureType) {
+                        continue; // Skip unresolved feature types.
+                    }
+                } else {
+                    valueType = ((FeatureAssociationRole) property).getValueType();
+                }
+                if (name.equals(valueType.getName())) {
+                    return valueType;
+                }
+                deferred.add(valueType);
+            }
+        }
+        /*
+         * Search in inherited associations as a separated step, in order to include the overridden
+         * associations in the search. Overridden associations have the same association role name,
+         * but not necessarily the same feature type (may be a subtype). This is equivalent to
+         * "covariant return type" in the Java language.
+         */
+        for (FeatureType type : feature.getSuperTypes()) {
+            if (name.equals(type.getName())) {
+                return type;
+            }
+            type = search(type, name, deferred);
+            if (type != null) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 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<FeatureType,Boolean>(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.
+     * @throws IllegalStateException if the feature type has been specified
+     *         {@linkplain #DefaultAssociationRole(Map, GenericName, int, int) only by its name}
+     *         and not yet resolved.
      */
     @Override
     public final FeatureType getValueType() {
-        return valueType;
+        /*
+         * This method shall be final for consistency with other methods in this classes
+         * which use the 'valueType' field directly. Furthermore, this method is invoked
+         * (indirectly) by DefaultFeatureType constructors.
+         */
+        final FeatureType type = valueType;
+        if (type instanceof NamedFeatureType) {
+            throw new IllegalStateException(Errors.format(Errors.Keys.UnresolvedFeatureName_1, getName()));
+        }
+        return type;
+    }
+
+    /**
+     * Returns the name of the feature type. This information is always available
+     * even when the name has not yet been {@linkplain #resolve resolved}.
+     */
+    static GenericName getValueTypeName(final FeatureAssociationRole role) {
+        return (role instanceof DefaultAssociationRole ? ((DefaultAssociationRole) role).valueType : role.getValueType()).getName();
     }
 
     /**
@@ -198,7 +392,12 @@ public class DefaultAssociationRole exte
      */
     @Override
     public int hashCode() {
-        return super.hashCode() + valueType.hashCode();
+        /*
+         * Do not use the full 'valueType' object for computing hash code,
+         * because it may change before and after 'resolve' is invoked. In
+         * addition, this avoid infinite recursivity in case of cyclic graph.
+         */
+        return super.hashCode() + valueType.getName().hashCode();
     }
 
     /**

Modified: sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] Fri Oct 24 17:28:28 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;
@@ -105,7 +106,7 @@ public class DefaultFeatureType extends 
 
     /**
      * {@code true} if this feature type contains only attributes constrained to the [1 … 1] cardinality,
-     * or operations.
+     * or operations. The feature type shall not contains associations.
      *
      * @see #isSimple()
      */
@@ -118,6 +119,23 @@ public class DefaultFeatureType extends 
     private transient boolean isSparse;
 
     /**
+     * {@code true} if we determined that this feature type does not have, directly or indirectly,
+     * any unresolved name (i.e. a {@link DefaultAssociationRole#valueType} specified only be the
+     * feature type name instead than its actual instance). A value of {@code true} means that all
+     * names have been resolved. However a value of {@code false} only means that we are not sure,
+     * and that {@link #resolve(FeatureType)} should check again.
+     *
+     * <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 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;
+
+    /**
      * The direct parents of this feature type, or an empty set if none.
      *
      * @see #getSuperTypes()
@@ -211,14 +229,24 @@ public class DefaultFeatureType extends 
         super(identification);
         ArgumentChecks.ensureNonNull("properties", properties);
         this.isAbstract = isAbstract;
-        this.superTypes = (superTypes == null) ? Collections.<FeatureType>emptySet() :
-                          CollectionsExt.<FeatureType>immutableSet(true, superTypes);
+        if (superTypes == null) {
+            this.superTypes = Collections.emptySet();
+        } else {
+            this.superTypes = CollectionsExt.immutableSet(true, superTypes);
+            for (final FeatureType type : this.superTypes) {
+                if (type instanceof NamedFeatureType) {
+                    // Hierarchy of feature types can not be cyclic.
+                    throw new IllegalArgumentException(Errors.format(Errors.Keys.UnresolvedFeatureName_1, type.getName()));
+                }
+            }
+        }
         switch (properties.length) {
             case 0:  this.properties = Collections.emptyList(); break;
             case 1:  this.properties = Collections.singletonList(properties[0]); break;
             default: this.properties = UnmodifiableArrayList.wrap(Arrays.copyOf(properties, properties.length, PropertyType[].class)); break;
         }
         computeTransientFields();
+        isResolved = resolve(this, null, isSimple);
     }
 
     /**
@@ -240,10 +268,11 @@ public class DefaultFeatureType extends 
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         computeTransientFields();
+        isResolved = isSimple; // Conservative value. The 'resolve' method will compute a more accurate value if needed.
     }
 
     /**
-     * Computes all transient fields ({@link #assignableTo}, {@link #byName}, {@link #indices}, {@link #isSimple}).
+     * Computes transient fields ({@link #assignableTo}, {@link #byName}, {@link #indices}, {@link #isSimple}).
      *
      * <p>As a side effect, this method checks for missing or duplicated names.</p>
      *
@@ -254,9 +283,9 @@ public class DefaultFeatureType extends 
         byName       = new LinkedHashMap<String,PropertyType>(capacity);
         indices      = new LinkedHashMap<String,Integer>(capacity);
         assignableTo = new HashSet<GenericName>(4);
-        assignableTo.add(getName());
+        assignableTo.add(super.getName());
         scanPropertiesFrom(this);
-        byName        = compact(byName);
+        byName        = CollectionsExt.compact(byName);
         assignableTo  = CollectionsExt.unmodifiableOrCopy(assignableTo);
         allProperties = byName.values();
         if (byName instanceof HashMap<?,?>) {
@@ -291,7 +320,7 @@ public class DefaultFeatureType extends 
                 }
             }
         }
-        indices = compact(indices);
+        indices = CollectionsExt.compact(indices);
         /*
          * Rational for choosing whether the feature is sparse: By default, java.util.HashMap implementation creates
          * an internal array of length 16 (see HashMap.DEFAULT_INITIAL_CAPACITY).  In addition, the HashMap instance
@@ -306,25 +335,15 @@ public class DefaultFeatureType extends 
     }
 
     /**
-     * Returns a more compact representation of the given map. This method is similar to
-     * {@link CollectionsExt#unmodifiableOrCopy(Map)}, except that it does not wrap the
-     * map in an unmodifiable view. The intend is to avoid one level of indirection for
-     * performance and memory reasons (keeping in mind that we will have lot of features).
-     * This is okay if we guaranteed that the map does not escape outside this class.
-     */
-    private static <K,V> Map<K,V> compact(final Map<K,V> map) {
-        switch (map.size()) {
-            case 0:  return Collections.emptyMap();
-            case 1:  final Map.Entry<K,V> entry = map.entrySet().iterator().next();
-                     return Collections.singletonMap(entry.getKey(), entry.getValue());
-            default: return map;
-        }
-    }
-
-    /**
      * Fills the {@link #byName} map using the non-transient information in the given {@code source}.
      * This method invokes itself recursively in order to use the information provided in super-types.
      * This method also performs an opportunist verification of argument validity.
+     *
+     * <p>{@code this} shall be the instance in process of being created, not any other instance
+     * (i.e. recursive method invocations are performed on the same {@code this} instance).</p>
+     *
+     * @param  source The feature from which to get properties.
+     * @throws IllegalArgumentException if two properties have the same name.
      */
     private void scanPropertiesFrom(final FeatureType source) {
         for (final FeatureType parent : source.getSuperTypes()) {
@@ -369,6 +388,8 @@ public class DefaultFeatureType extends 
      * Returns the string representation of the given name, making sure that the name is non-null
      * and the string non-empty. This method is used for checking argument validity.
      *
+     * <p>{@code this} shall be the instance in process of being created, not any other instance.</p>
+     *
      * @param name   The name for which to get the string representation.
      * @param source The feature which contains the property (typically {@code this}).
      * @param index  Index of the property having the given name.
@@ -390,6 +411,82 @@ public class DefaultFeatureType extends 
                 b.append("properties[").append(index).append("].name").toString()));
     }
 
+    /**
+     * If an associated feature type is a placeholder for a {@code FeatureType} to be defined later,
+     * replaces the placeholder by the actual instance if available. Otherwise do nothing.
+     *
+     * <p>This method is needed only in case of cyclic graph, e.g. feature <var>A</var> has an association
+     * to feature <var>B</var> which has an association back to <var>A</var>. It may also be <var>A</var>
+     * having an association to itself, <i>etc.</i></p>
+     *
+     * <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  previous Previous results, for avoiding never ending loop.
+     * @return {@code true} if all names have been resolved.
+     */
+    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
+         * instance, conservatively assumes 'isSimple'. It may cause more calculation than needed, but should not
+         * change the result.
+         */
+        if (feature instanceof DefaultFeatureType) {
+            final DefaultFeatureType dt = (DefaultFeatureType) feature;
+            return dt.isResolved = resolve(feature, previous, dt.isResolved);
+        } else {
+            return resolve(feature, previous, feature.isSimple());
+        }
+    }
+
+    /**
+     * 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, Map<FeatureType,Boolean> previous, boolean resolved) {
+        if (!resolved) {
+            resolved = true;
+            for (final FeatureType type : feature.getSuperTypes()) {
+                resolved &= resolve(type, previous);
+            }
+            for (final PropertyType property : feature.getProperties(false)) {
+                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<FeatureType,Boolean>(8);
+                        }
+                        Boolean r = previous.put(valueType, Boolean.FALSE);
+                        if (r == null) {
+                            r = resolve(valueType, previous);
+                            previous.put(valueType, r);
+                        }
+                        resolved &= r;
+                    }
+                }
+            }
+        }
+        return resolved;
+    }
+
 
     // -------- END OF CONSTRUCTORS ------------------------------------------------------------------------------
 
@@ -415,7 +512,8 @@ public class DefaultFeatureType extends 
 
     /**
      * Returns {@code true} if this feature type contains only attributes constrained to the [1 … 1] cardinality,
-     * or operations. Such feature types can be handled as a {@link org.opengis.util.Record}s.
+     * or operations (no feature association).
+     * Such feature types can be handled as a {@linkplain org.apache.sis.util.iso.DefaultRecord records}.
      *
      * @return {@code true} if this feature type contains only simple attributes or operations.
      */
@@ -542,10 +640,16 @@ public class DefaultFeatureType extends 
      * if we compare {@code FeatureType} to {@link Class} in the Java language, then this method is equivalent
      * to {@link Class#getSuperclass()} except that feature types allow multi-inheritance.</div>
      *
+     * <div class="note"><b>Note for subclasses:</b>
+     * this method is final because it is invoked (indirectly) by constructors, and invoking a user-overrideable
+     * method at construction time is not recommended. Furthermore, many Apache SIS methods need guarantees about
+     * the stability of this collection.
+     * </div>
+     *
      * @return The parents of this feature type, or an empty set if none.
      */
     @Override
-    public Set<FeatureType> getSuperTypes() {
+    public final Set<FeatureType> getSuperTypes() {
         return superTypes;
     }
 
@@ -555,13 +659,19 @@ public class DefaultFeatureType extends 
      * inherited from the {@linkplain #getSuperTypes() super-types} only if {@code includeSuperTypes}
      * is {@code true}.
      *
+     * <div class="note"><b>Note for subclasses:</b>
+     * this method is final because it is invoked (indirectly) by constructors, and invoking a user-overrideable
+     * method at construction time is not recommended. Furthermore, many Apache SIS methods need guarantees about
+     * the stability of this collection.
+     * </div>
+     *
      * @param  includeSuperTypes {@code true} for including the properties inherited from the super-types,
      *         or {@code false} for returning only the properties defined explicitely in this type.
      * @return Feature operation, attribute type and association role that carries characteristics of this
      *         feature type (not including parent types).
      */
     @Override
-    public Collection<PropertyType> getProperties(final boolean includeSuperTypes) {
+    public final Collection<PropertyType> getProperties(final boolean includeSuperTypes) {
         return includeSuperTypes ? allProperties : properties;
     }
 

Modified: sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -220,7 +220,7 @@ header: for (int i=0; ; i++) {
                 final FeatureAssociationRole pt = (FeatureAssociationRole) propertyType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
-                valueType     = toString(pt.getValueType().getName());
+                valueType     = toString(DefaultAssociationRole.getValueTypeName(pt));
                 valueClass    = Feature.class;
             } else if (propertyType instanceof Operation) {
                 final IdentifiedType resultType = ((Operation) propertyType).getResult();

Modified: sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -16,11 +16,18 @@
  */
 package org.apache.sis.feature;
 
+import java.util.Map;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.util.GenericName;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static java.util.Collections.singletonMap;
+import static org.apache.sis.feature.DefaultAssociationRole.NAME_KEY;
+import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
@@ -40,27 +47,57 @@ public final strictfp class DefaultAssoc
     /**
      * Creates an association to a twin town. We arbitrarily fix the maximum number
      * of occurrences to 1, even if in reality some cities have many twin towns.
+     *
+     * @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() {
-        return new DefaultAssociationRole(singletonMap(DefaultAssociationRole.NAME_KEY, "twin town"),
-                DefaultFeatureTypeTest.city(), 0, 1);
+    static DefaultAssociationRole twinTown(final boolean cyclic) {
+        final Map<String,?> properties = singletonMap(NAME_KEY, "twin town");
+        if (cyclic) {
+            final GenericName valueType = DefaultFactories.SIS_NAMES.createTypeName(null, "Twin town");
+            return new DefaultAssociationRole(properties, valueType, 0, 1);
+        } else {
+            final DefaultFeatureType valueType = DefaultFeatureTypeTest.city();
+            return new DefaultAssociationRole(properties, valueType, 0, 1);
+        }
     }
 
     /**
      * Returns a City feature type which may have a twin town.
+     *
+     * @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 DefaultAssociationRole twinTown = twinTown();
-        return new DefaultFeatureType(singletonMap(DefaultFeatureType.NAME_KEY, "Twin town"), false,
-                new FeatureType[] {twinTown.getValueType()}, twinTown);
+    static DefaultFeatureType twinTownCity(final boolean cyclic) {
+        final DefaultAssociationRole twinTown = twinTown(cyclic);
+        final FeatureType parent = cyclic ? DefaultFeatureTypeTest.city() : twinTown.getValueType();
+        return createType("Twin town", parent, twinTown);
+    }
+
+    /**
+     * Convenience method creating a feature type of the given name, parent and property.
+     *
+     * @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)
+    {
+        return new DefaultFeatureType(singletonMap(NAME_KEY, name),
+                false, new FeatureType[] {parent}, property);
     }
 
     /**
      * Tests serialization of an {@link DefaultAssociationRole} instance.
+     * This will also indirectly tests {@link DefaultAssociationRole#equals(Object)}.
      */
     @Test
     public void testSerialization() {
-        assertSerializedEquals(twinTown());
+        assertSerializedEquals(twinTown(false));
     }
 
     /**
@@ -68,7 +105,7 @@ public final strictfp class DefaultAssoc
      */
     @Test
     public void testGetTitleProperty() {
-        final DefaultAssociationRole twinTown = twinTown();
+        final DefaultAssociationRole twinTown = twinTown(false);
         assertEquals("city", DefaultAssociationRole.getTitleProperty(twinTown));
     }
 
@@ -77,7 +114,91 @@ public final strictfp class DefaultAssoc
      */
     @Test
     public void testToString() {
-        final DefaultAssociationRole twinTown = twinTown();
+        final DefaultAssociationRole twinTown = twinTown(false);
         assertEquals("FeatureAssociationRole[“twin town” : City]", twinTown.toString());
     }
+
+    /**
+     * Tests a bidirectional association (a feature having an association to itself).
+     */
+    @Test
+    public void testBidirectionalAssociation() {
+        final DefaultFeatureType twinTown = twinTownCity(true);
+        final FeatureAssociationRole association = (FeatureAssociationRole) twinTown.getProperty("twin town");
+        assertSame("twinTown.property(“twin town”).valueType", twinTown, association.getValueType());
+        /*
+         * Creates a FeatureType copy containing the same properties. Used for verifying
+         * that 'DefaultFeatureType.equals(Object)' does not fall in an infinite loop.
+         */
+        final DefaultFeatureType copy = createType(twinTown.getName(),
+                getSingleton(twinTown.getSuperTypes()), association);
+
+        assertTrue("equals", copy.equals(twinTown));
+        assertTrue("equals", twinTown.equals(copy));
+        assertEquals("hashCode", copy.hashCode(), twinTown.hashCode());
+    }
+
+    /**
+     * Tests {@link DefaultFeatureType#isAssignableFrom(FeatureType)} and {@link DefaultFeatureType#equals(Object)}
+     * on a feature type having a bidirectional association to an other feature. This test will fall in an infinite
+     * loop if the implementation does not have proper guard against infinite recursivity.
+     */
+    @Test
+    @DependsOnMethod("testBidirectionalAssociation")
+    public void testCyclicAssociation() {
+        final GenericName nameOfA = DefaultFactories.SIS_NAMES.createTypeName(null, "A");
+        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);
+        /*
+         * 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", 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));
+        assertFalse("equals", typeC.equals(typeB));
+        assertFalse("hashCode", typeA.hashCode() == typeB.hashCode());
+        assertFalse("hashCode", typeC.hashCode() == typeD.hashCode());
+    }
 }

Modified: sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -63,7 +63,7 @@ public abstract strictfp class FeatureTe
      * Creates a feature for twin towns.
      */
     static AbstractFeature twinTown(final boolean isSparse) {
-        final DefaultFeatureType twinTown = DefaultAssociationRoleTest.twinTownCity();
+        final DefaultFeatureType twinTown = DefaultAssociationRoleTest.twinTownCity(false);
 
         final AbstractFeature leMans = isSparse ? new SparseFeature(twinTown) : new DenseFeature(twinTown);
         leMans.setPropertyValue("city", "Le Mans");

Modified: sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -51,7 +51,7 @@ public final strictfp class SingletonAss
         final Feature twinTown = DefaultFeatureTypeTest.city().newInstance();
         twinTown.setPropertyValue("city", "Le Mans");
         twinTown.setPropertyValue("population", 143240); // In 2011.
-        final AbstractAssociation association = new SingletonAssociation(DefaultAssociationRoleTest.twinTown());
+        final AbstractAssociation association = new SingletonAssociation(DefaultAssociationRoleTest.twinTown(false));
         association.setValue(twinTown);
         return association;
     }

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java [UTF-8] Fri Oct 24 17:28:28 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/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java [UTF-8] Fri Oct 24 17:28:28 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/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -144,7 +144,7 @@ public abstract class AbstractMetadata i
      */
     @Override
     public boolean isEmpty() {
-        return Pruner.isEmpty(this, false);
+        return Pruner.isEmpty(this, true, false);
     }
 
     /**
@@ -155,7 +155,7 @@ public abstract class AbstractMetadata i
      * @throws UnmodifiableMetadataException If this metadata is not modifiable.
      */
     public void prune() {
-        Pruner.isEmpty(this, true);
+        Pruner.isEmpty(this, true, true);
     }
 
     /**

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -57,8 +57,14 @@ final class Pruner {
      * Returns the metadata properties. When used for pruning empty values, the map needs to
      * include empty (but non-null) values in order to allow us to set them to {@code null}.
      */
-    private static Map<String, Object> asMap(final MetadataStandard standard, final Object metadata, final boolean prune) {
-        return standard.asValueMap(metadata, KeyNamePolicy.JAVABEANS_PROPERTY, prune ? NON_NULL : NON_EMPTY);
+    private static Map<String, Object> asMap(final MetadataStandard standard, final Object metadata,
+            final boolean mandatory, final boolean prune)
+    {
+        final PropertyAccessor accessor = standard.getAccessor(metadata.getClass(), mandatory);
+        if (accessor != null) {
+            return new ValueMap(metadata, accessor, KeyNamePolicy.JAVABEANS_PROPERTY, prune ? NON_NULL : NON_EMPTY);
+        }
+        return null;
     }
 
     /**
@@ -78,12 +84,16 @@ final class Pruner {
      * It creates a map of visited nodes when the iteration begin, and deletes that map when the
      * iteration ends.</p>
      *
-     * @param  metadata The metadata object.
-     * @param  prune {@code true} for deleting empty entries.
+     * @param  metadata  The metadata object.
+     * @param  mandatory {@code true} if we shall throw an exception if {@code metadata} is not of the expected class.
+     * @param  prune     {@code true} for deleting empty entries.
      * @return {@code true} if all metadata properties are null or empty.
      */
-    static boolean isEmpty(final AbstractMetadata metadata, final boolean prune) {
-        final Map<String,Object> properties = asMap(metadata.getStandard(), metadata, prune);
+    static boolean isEmpty(final AbstractMetadata metadata, final boolean mandatory, final boolean prune) {
+        final Map<String,Object> properties = asMap(metadata.getStandard(), metadata, mandatory, prune);
+        if (properties == null) {
+            return false; // For metadata of unknown class, conservatively assume non-empty.
+        }
         final Map<Object,Boolean> tested = MAPS.get();
         if (!tested.isEmpty()) {
             return isEmpty(properties, tested, prune);
@@ -165,7 +175,7 @@ final class Pruner {
                         } else if (!(element instanceof Enumerated)) {
                             final MetadataStandard standard = MetadataStandard.forClass(element.getClass());
                             if (standard != null) {
-                                isEmptyElement = isEmpty(asMap(standard, element, prune), tested, prune);
+                                isEmptyElement = isEmpty(asMap(standard, element, false, prune), tested, prune);
                                 if (!isEmptyElement && element instanceof Emptiable) {
                                     isEmptyElement = ((Emptiable) element).isEmpty();
                                 }

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -260,8 +260,7 @@ public class DefaultMetadata extends ISO
      *
      * @param contact   Party responsible for the metadata information.
      * @param dateStamp Date that the metadata was created.
-     * @param identificationInfo Basic information about the resource
-     *        to which the metadata applies.
+     * @param identificationInfo Basic information about the resource to which the metadata applies.
      */
     public DefaultMetadata(final Responsibility contact,
                            final Date           dateStamp,
@@ -542,7 +541,7 @@ public class DefaultMetadata extends ISO
     @Override
     @Deprecated
     @XmlElement(name = "characterSet")
-    public final Charset getCharacterSet()  {
+    public final Charset getCharacterSet() {
         return LegacyPropertyAdapter.getSingleton(characterSets, Charset.class, null, DefaultMetadata.class, "getCharacterSet");
     }
 
@@ -646,40 +645,6 @@ public class DefaultMetadata extends ISO
     }
 
     /**
-     * A specialization of {@link LegacyPropertyAdapter} which will try to merge the
-     * {@code "hierarchyLevel"} and {@code "hierarchyLevelName"} properties in the same
-     * {@link DefaultMetadataScope} instance.
-     */
-    private static abstract class ScopeAdapter<L> extends LegacyPropertyAdapter<L,MetadataScope> {
-        /**
-         * @param scopes Value of {@link DefaultMetadata#getMetadataScopes()}.
-         */
-        ScopeAdapter(final Collection<MetadataScope> scopes)  {
-            super(scopes);
-        }
-
-        /**
-         * Invoked (indirectly) by JAXB when adding a new scope code or scope name. This implementation searches
-         * for an existing {@link MetadataScope} instance with a free slot for the new value before to create a
-         * new {@link DefaultMetadataScope} instance.
-         */
-        @Override
-        public boolean add(final L newValue) {
-            final Iterator<MetadataScope> it = elements.iterator();
-            if (it.hasNext()) {
-                MetadataScope scope = it.next();
-                if (unwrap(scope) == null) {
-                    if (!(scope instanceof DefaultMetadataScope)) {
-                        scope = new DefaultMetadataScope(scope);
-                    }
-                    return update(scope, newValue);
-                }
-            }
-            return super.add(newValue);
-        }
-    }
-
-    /**
      * Returns the scope to which the metadata applies.
      *
      * @return Scope to which the metadata applies.
@@ -691,10 +656,10 @@ public class DefaultMetadata extends ISO
     @Deprecated
     @XmlElement(name = "hierarchyLevel")
     public final Collection<ScopeCode> getHierarchyLevels() {
-        return new ScopeAdapter<ScopeCode>(getMetadataScopes()) {
+        return new MetadataScopeAdapter<ScopeCode>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
             @Override protected MetadataScope wrap(final ScopeCode value) {
-                return new DefaultMetadataScope(value);
+                return new DefaultMetadataScope(value, null);
             }
 
             /** Extracts the legacy value from the new kind of value. */
@@ -718,8 +683,8 @@ public class DefaultMetadata extends ISO
      *
      * @param newValues The new hierarchy levels.
      *
-     * @deprecated As of ISO 19115:2014, replaced by {@link #getMetadataScopes()}
-     *   followed by {@link DefaultMetadataScope#setResourceScope(ScopeCode)}.
+     * @deprecated As of ISO 19115:2014, replaced by {@link #setMetadataScopes(Collection)}
+     *   and {@link DefaultMetadataScope#setResourceScope(ScopeCode)}.
      */
     @Deprecated
     public final void setHierarchyLevels(final Collection<? extends ScopeCode> newValues) {
@@ -739,12 +704,10 @@ public class DefaultMetadata extends ISO
     @Deprecated
     @XmlElement(name = "hierarchyLevelName")
     public final Collection<String> getHierarchyLevelNames() {
-        return new ScopeAdapter<String>(getMetadataScopes()) {
+        return new MetadataScopeAdapter<String>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
             @Override protected MetadataScope wrap(final String value) {
-                final DefaultMetadataScope scope = new DefaultMetadataScope();
-                scope.setName(new SimpleInternationalString(value));
-                return scope;
+                return new DefaultMetadataScope(null, value);
             }
 
             /** Extracts the legacy value from the new kind of value. */
@@ -756,7 +719,7 @@ public class DefaultMetadata extends ISO
             /** Updates the legacy value in an existing instance of the new kind of value. */
             @Override protected boolean update(final MetadataScope container, final String value) {
                 if (container instanceof DefaultMetadataScope) {
-                    ((DefaultMetadataScope) container).setName(new SimpleInternationalString(value));
+                    ((DefaultMetadataScope) container).setName(value != null ? new SimpleInternationalString(value) : null);
                     return true;
                 }
                 return false;
@@ -769,8 +732,8 @@ public class DefaultMetadata extends ISO
      *
      * @param newValues The new hierarchy level names.
      *
-     * @deprecated As of ISO 19115:2014, replaced by {@link #getMetadataScopes()}
-     *   followed by {@link DefaultMetadataScope#setName(InternationalString)}.
+     * @deprecated As of ISO 19115:2014, replaced by {@link #setMetadataScopes(Collection)}
+     *   and {@link DefaultMetadataScope#setName(InternationalString)}.
      */
     @Deprecated
     public final void setHierarchyLevelNames(final Collection<? extends String> newValues) {

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -20,6 +20,7 @@ import javax.xml.bind.annotation.XmlType
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.InternationalString;
+import org.apache.sis.util.iso.Types;
 import org.opengis.metadata.MetadataScope;
 import org.opengis.metadata.maintenance.ScopeCode;
 
@@ -73,9 +74,11 @@ public class DefaultMetadataScope extend
      * Constructs a metadata scope initialized to the given value.
      *
      * @param resourceScope code for the scope.
+     * @param name Description of the scope, or {@code null} if none.
      */
-    public DefaultMetadataScope(final ScopeCode resourceScope) {
+    public DefaultMetadataScope(final ScopeCode resourceScope, final CharSequence name) {
         this.resourceScope = resourceScope;
+        this.name = Types.toInternationalString(name);
     }
 
     /**

Modified: sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java [UTF-8] Fri Oct 24 17:28:28 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/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java [UTF-8] Fri Oct 24 17:28:28 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/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java [UTF-8] Fri Oct 24 17:28:28 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/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java?rev=1634099&r1=1634098&r2=1634099&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java [UTF-8] Fri Oct 24 17:28:28 2014
@@ -298,11 +298,11 @@ public class DefaultScopeDescription ext
      * “<cite>Administrative area A — Road network</cite>” description.
      * </div>
      *
-     * @return Feature types to which the information applies.
-     *
      * {@section Conditions}
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
+     *
+     * @return Feature types to which the information applies.
      */
     @Override
     public Set<CharSequence> getFeatures() {
@@ -331,11 +331,11 @@ public class DefaultScopeDescription ext
      * “<cite>Administrative area A — Overhead clearance</cite>” description.
      * </div>
      *
-     * @return Attribute types to which the information applies.
-     *
      * {@section Conditions}
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
+     *
+     * @return Attribute types to which the information applies.
      */
     @Override
     public Set<CharSequence> getAttributes() {
@@ -364,11 +364,11 @@ public class DefaultScopeDescription ext
      * “<cite>Administrative area A — New bridge</cite>” description.
      * </div>
      *
-     * @return Feature instances to which the information applies.
-     *
      * {@section Conditions}
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
+     *
+     * @return Feature instances to which the information applies.
      */
     @Override
     public Set<CharSequence> getFeatureInstances() {
@@ -397,11 +397,11 @@ public class DefaultScopeDescription ext
      * “<cite>Administrative area A — New bridge — Overhead clearance</cite>” description.
      * </div>
      *
-     * @return Attribute instances to which the information applies.
-     *
      * {@section Conditions}
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
+     *
+     * @return Attribute instances to which the information applies.
      */
     @Override
     public Set<CharSequence> getAttributeInstances() {



Mime
View raw message