sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1822963 - in /sis/branches/ISO-19115-3/core: sis-metadata/src/test/java/org/apache/sis/metadata/ sis-metadata/src/test/java/org/apache/sis/metadata/iso/ sis-metadata/src/test/java/org/apache/sis/test/xml/ sis-utility/src/test/java/org/apac...
Date Fri, 02 Feb 2018 14:01:31 GMT
Author: desruisseaux
Date: Fri Feb  2 14:01:31 2018
New Revision: 1822963

URL: http://svn.apache.org/viewvc?rev=1822963&view=rev
Log:
Move PackageVerifier in a separated class and move AnnotationsTestCase to the org.apache.sis.test.xml package.

Added:
    sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataConsistencyCheck.java
      - copied, changed from r1822962, sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
    sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
      - copied, changed from r1822962, sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java
    sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java
      - copied, changed from r1822957, sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java
Removed:
    sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
    sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java
Modified:
    sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
    sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java

Copied: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataConsistencyCheck.java (from r1822962, sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java)
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataConsistencyCheck.java?p2=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataConsistencyCheck.java&p1=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java&r1=1822962&r2=1822963&rev=1822963&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataConsistencyCheck.java [UTF-8] Fri Feb  2 14:01:31 2018
@@ -29,7 +29,7 @@ import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.metadata.Dependencies;
-import org.apache.sis.test.AnnotationsTestCase;
+import org.apache.sis.test.xml.AnnotationConsistencyCheck;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
@@ -39,7 +39,7 @@ import static org.opengis.test.Assert.*;
 
 /**
  * Base class for tests done on metadata objects using reflection. This base class tests JAXB annotations
- * as described in the {@linkplain AnnotationsTestCase parent class}, and tests additional aspects like:
+ * as described in the {@link AnnotationConsistencyCheck parent class}, and tests additional aspects like:
  *
  * <ul>
  *   <li>All {@link AbstractMetadata} instance shall be initially {@linkplain AbstractMetadata#isEmpty() empty}.</li>
@@ -47,13 +47,16 @@ import static org.opengis.test.Assert.*;
  *   <li>After a call to a setter method, the getter method shall return a value equals to the given value.</li>
  * </ul>
  *
+ * This base class is defined in this {@code org.apache.sis.metadata} package because it needs to access
+ * package-private classes.
+ *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
 @DependsOn(PropertyAccessorTest.class)
-public abstract strictfp class MetadataTestCase extends AnnotationsTestCase {
+public abstract strictfp class MetadataConsistencyCheck extends AnnotationConsistencyCheck {
     /**
      * The standard implemented by the metadata objects to test.
      */
@@ -70,7 +73,7 @@ public abstract strictfp class MetadataT
      * @param  standard  the standard implemented by the metadata objects to test.
      * @param  types     the GeoAPI interfaces, {@link CodeList} or {@link Enum} types to test.
      */
-    protected MetadataTestCase(final MetadataStandard standard, final Class<?>... types) {
+    protected MetadataConsistencyCheck(final MetadataStandard standard, final Class<?>... types) {
         super(types);
         this.standard = standard;
     }

Modified: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java?rev=1822963&r1=1822962&r2=1822963&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java [UTF-8] Fri Feb  2 14:01:31 2018
@@ -23,7 +23,7 @@ import org.opengis.annotation.UML;
 import org.opengis.annotation.Specification;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.metadata.MetadataStandard;
-import org.apache.sis.metadata.MetadataTestCase;
+import org.apache.sis.metadata.MetadataConsistencyCheck;
 import org.apache.sis.test.LoggingWatcher;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.xml.Namespaces;
@@ -37,12 +37,12 @@ import static org.junit.Assert.*;
  * Tests all known {@link ISOMetadata} subclasses for JAXB annotations and getter/setter methods.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.0
  * @since   0.3
  * @module
  */
 @DependsOn(org.apache.sis.metadata.PropertyAccessorTest.class)
-public final strictfp class AllMetadataTest extends MetadataTestCase {
+public final strictfp class AllMetadataTest extends MetadataConsistencyCheck {
     /**
      * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
      * do so, but should be considered as an implementation details (it should have been a private field).
@@ -212,7 +212,7 @@ public final strictfp class AllMetadataT
     }
 
     /**
-     * Performs the test documented in the {@link MetadataTestCase} javadoc.
+     * Performs the test documented in the {@link MetadataConsistencyCheck} javadoc.
      */
     @Test
     @Override

Copied: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java (from r1822962, sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java)
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java?p2=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java&p1=sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java&r1=1822962&r2=1822963&rev=1822963&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java [UTF-8] Fri Feb  2 14:01:31 2018
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.test;
+package org.apache.sis.test.xml;
 
 import java.util.Set;
 import java.util.HashSet;
@@ -34,6 +34,8 @@ import org.opengis.annotation.Obligation
 import org.opengis.annotation.Specification;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.xml.Namespaces;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.TestCase;
 import org.junit.After;
 import org.junit.Test;
 
@@ -42,7 +44,7 @@ import static org.apache.sis.test.TestUt
 
 
 /**
- * Base class for validations of {@link UML}, {@link XmlElement} and other annotations.
+ * Verifies consistency between {@link UML}, {@link XmlElement} and other annotations.
  * Some tests performed by this class are:
  *
  * <ul>
@@ -61,17 +63,20 @@ import static org.apache.sis.test.TestUt
  *   <li>The {@linkplain #getWrapperFor wrapper}, if any, is consistent.</li>
  * </ul>
  *
+ * This class does not verify JAXB annotations against a XSD file.
+ * For such verification, see {@link SchemaCompliance}.
+ *
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.0
  * @since   0.3
  * @module
  */
-public abstract strictfp class AnnotationsTestCase extends TestCase {
+public abstract strictfp class AnnotationConsistencyCheck extends TestCase {
     /**
      * The {@value} string used in JAXB annotations for default names or namespaces.
      */
-    private static final String DEFAULT = "##default";
+    static final String DEFAULT = "##default";
 
     /**
      * The GeoAPI interfaces, {@link CodeList} or {@link Enum} types to test.
@@ -97,7 +102,7 @@ public abstract strictfp class Annotatio
      *
      * @param types The GeoAPI interfaces, {@link CodeList} or {@link Enum} types to test.
      */
-    protected AnnotationsTestCase(final Class<?>... types) {
+    protected AnnotationConsistencyCheck(final Class<?>... types) {
         this.types = types;
     }
 
@@ -153,9 +158,8 @@ public abstract strictfp class Annotatio
     protected abstract Class<?> getWrapperFor(Class<?> type) throws ClassNotFoundException;
 
     /**
-     * The value returned by {@link AnnotationsTestCase#getWrapperFor(Class)}, together with
-     * a boolean telling whether the wrapper has been found in the tested class or in one
-     * of its parent classes.
+     * The value returned by {@link #getWrapperFor(Class)}, together with a boolean telling
+     * whether the wrapper has been found in the tested class or in one of its parent classes.
      */
     private static final class WrapperClass {
         final Class<?> type;

Copied: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java (from r1822957, sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java)
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java?p2=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java&p1=sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java&r1=1822957&r2=1822963&rev=1822963&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java [UTF-8] Fri Feb  2 14:01:31 2018
@@ -17,67 +17,33 @@
 package org.apache.sis.test.xml;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryIteratorException;
 import java.util.Map;
-import java.util.Set;
-import java.util.Deque;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.ArrayDeque;
+import java.lang.reflect.Method;
 import java.util.Arrays;
-import java.util.Objects;
 import java.util.Collections;
-import java.lang.reflect.Method;
-import javax.xml.XMLConstants;
+import java.util.HashSet;
+import java.util.Set;
 import javax.xml.bind.annotation.XmlNs;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlSchema;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-import org.w3c.dom.Node;
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
 import org.xml.sax.SAXException;
-import org.apache.sis.util.StringBuilders;
 import org.apache.sis.internal.jaxb.LegacyNamespaces;
 
 
 /**
- * Compares JAXB annotations against the ISO 19115 schema. This test requires a connection to
- * <a href="http://standards.iso.org/iso/19115/-3/">http://standards.iso.org/iso/19115/-3/</a>.
- * All classes in a given directory are scanned.
- *
- * <div class="section">Limitations</div>
- * Current implementation ignores the XML prefix (e.g. {@code "cit:"} in {@code "cit:CI_Citation"}).
- * We assume that there is no name collision, especially given that {@code "CI_"} prefix in front of
- * most OGC/ISO class names have the effect of a namespace. If a collision nevertheless happen, then
- * an exception will be thrown.
- *
- * <p>Current implementation assumes that XML element name, type name, property name and property type
- * name follow some naming convention. For example type names are suffixed with {@code "_Type"} in OGC
- * schemas, while property type names are suffixed with {@code "_PropertyType"}.  This class throws an
- * exception if a type does not follow the expected naming convention. This requirement makes
- * implementation easier, by reducing the amount of {@link Map}s that we need to manage.</p>
+ * Verify JAXB annotations in a single package. A new instance of this class is created by
+ * {@link SchemaCompliance#verify(java.nio.file.Path)} for each Java package to be verified.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
  */
-public final strictfp class SchemaCompliance {
-    /**
-     * The root of ISO schemas. May be replaced by {@link #schemaRootDirectory} if a local copy
-     * is available for faster tests.
-     */
-    private static final String SCHEMA_ROOT_DIRECTORY = "http://standards.iso.org/iso/";
-
+final strictfp class PackageVerifier {
     /**
      * Classes or properties having a JAXB annotation in this namespace should be deprecated.
      */
@@ -85,632 +51,171 @@ public final strictfp class SchemaCompli
             Arrays.asList(LegacyNamespaces.GMD, LegacyNamespaces.GMI, LegacyNamespaces.SRV)));
 
     /**
-     * ISO 19115-2 classes to merge with ISO 19115-1 classes. For example ISO 19115-2 defines {@code MI_Band}
-     * as an extension of ISO 19115-1 {@code MD_Band}, but GeoAPI and Apache SIS merges those two types in a
-     * single class for simplicity. Consequently when reading the schema, we rename some {@code MI_*} types
-     * as {@code MD_*} in order to store properties together.
-     */
-    private static final Map<String,String> TYPES_TO_MERGE;
-    static {
-        final Map<String,String> m = new HashMap<>();
-        // ………Merge what…………………………………………………………Into……………………………………………
-        m.put("MI_Band_Type",                 "MD_Band_Type");
-        m.put("MI_CoverageDescription_Type",  "MD_CoverageDescription_Type");
-        m.put("MI_Georectified_Type",         "MD_Georectified_Type");
-        m.put("LE_ProcessStep_Type",          "LI_ProcessStep_Type");
-        m.put("AbstractMX_File_Type",         "MX_DataFile_Type");
-        m.put("Abstract_DataQuality_Type",    "DQ_DataQuality_Type");
-        m.put("Abstract_QualityElement_Type", "AbstractDQ_Element_Type");
-        TYPES_TO_MERGE = Collections.unmodifiableMap(m);
-    }
-
-    /**
-     * The prefix of XML type names for properties. In ISO/OGC schemas, this prefix does not appear
-     * in the definition of class types but may appear in the definition of property types.
-     */
-    private static final String ABSTRACT_PREFIX = "Abstract_";
-
-    /**
-     * The suffix of XML type names for classes.
-     * This is used by convention in OGC/ISO standards (but not necessarily in other XSD).
-     */
-    private static final String TYPE_SUFFIX = "_Type";
-
-    /**
-     * The suffix of XML property type names in a given class.
-     * This is used by convention in OGC/ISO standards (but not necessarily in other XSD).
-     */
-    private static final String PROPERTY_TYPE_SUFFIX = "_PropertyType";
-
-    /**
-     * XML type to ignore because of key collisions in {@link #typeDefinitions}.
-     * Those collisions occur because code lists are defined as links to the same file,
-     * with only different anchor positions.
-     */
-    private static final String CODELIST_TYPE = "gco:CodeListValue_Type";
-
-    /**
-     * Separator between XML prefix and the actual name.
-     */
-    private static final char PREFIX_SEPARATOR = ':';
-
-    /**
-     * If the computer contains a local copy of ISO schemas, path to that directory. Otherwise {@code null}.
-     * If non-null, the {@code "http://standards.iso.org/iso/"} prefix in URL will be replaced by that path.
-     * This field is usually {@code null}, but can be set to a non-null value for making tests faster.
-     */
-    private final Path schemaRootDirectory;
-
-    /**
-     * Root directory from which to search for classes.
+     * The schemas to compare with the JAXB annotations.
+     * Additional schemas will be loaded as needed.
      */
-    private final Path classRootDirectory;
+    private final SchemaCompliance schemas;
 
     /**
-     * A temporary buffer for miscellaneous string operations.
-     * Valid only in a local scope since the content may change at any time.
+     * The package name, for reporting error.
      */
-    private final StringBuilder buffer;
+    private final String packageName;
 
     /**
-     * The DOM factory used for reading XSD schemas.
+     * The default namespace to use if a class does not define explicitely a namespace.
      */
-    private final DocumentBuilderFactory factory;
+    private final String defaultNS;
 
     /**
-     * URL of schemas loaded, for avoiding loading the same schema many time.
-     * The last element on the queue is the schema in process of being loaded,
-     * used for resolving relative paths in {@code <xs:include>} elements.
+     * Whether a namespace is actually used of not.
+     * We use this map for identifying unnecessary prefix declarations.
      */
-    private final Deque<String> schemaLocations;
-
-    /**
-     * The type and namespace of a property or class. Used in {@link #typeDefinitions} map.
-     */
-    private static final class Info {
-        final String  typeName;
-        final String  namespace;
-        final boolean isRequired;
-        final boolean isCollection;
-
-        Info(final String typeName, final String namespace, final boolean isRequired, final boolean isCollection) {
-            this.typeName     = typeName;
-            this.namespace    = namespace;
-            this.isRequired   = isRequired;
-            this.isCollection = isCollection;
-        }
-
-        boolean equal(final Info other) {
-            return Objects.equals(typeName,     other.typeName)
-                && Objects.equals(namespace,    other.namespace)
-                && isRequired   == other.isRequired
-                && isCollection == other.isCollection;
-        }
-
-        @Override public String toString() {
-            return typeName;
-        }
-    }
-
-    /**
-     * Definitions of XML type for each class. In OGC/ISO schemas, those definitions have the {@value #TYPE_SUFFIX}
-     * suffix in their name (which is omitted). The value is another map, where keys are property names and values
-     * are their types, having the {@link #PROPERTY_TYPE_SUFFIX} suffix in their name (which is omitted).
-     */
-    private final Map<String, Map<String,Info>> typeDefinitions;
-
-    /**
-     * Notifies that we are about to define the XML type for each property. In OGC/ISO schemas, those definitions
-     * have the {@value #PROPERTY_TYPE_SUFFIX} suffix in their name (which is omitted). After this method call,
-     * properties can be defined by calls to {@link #addProperty(String, String, boolean, boolean)}.
-     */
-    private void preparePropertyDefinitions(final String type) throws SchemaException {
-        currentProperties = typeDefinitions.computeIfAbsent(trim(type, TYPE_SUFFIX).intern(), (k) -> new HashMap<>());
-    }
-
-    /**
-     * The properties of the XML type under examination, or {@code null} if none.
-     * If non-null, this is one of the values in the {@link #typeDefinitions} map.
-     * By convention, the {@code null} key is associated to information about the class.
-     */
-    private Map<String,Info> currentProperties;
-
-    /**
-     * A single property type under examination, or {@code null} if none.
-     * If non-null, this is a value ending with the {@value #PROPERTY_TYPE_SUFFIX} suffix.
-     */
-    private String currentPropertyType;
-
-    /**
-     * Namespace of the type or properties being defined.
-     * This is specified by {@code <xs:schema targetNamespace="(…)">}.
-     */
-    private String targetNamespace;
-
-    /**
-     * The namespaces associated to prefixes, as declared by JAXB {@link XmlNs} annotations.
-     * Used for verifying that no prefix is defined twice for different namespaces.
-     */
-    private final Map<String,String> allXmlNS;
-
-    /**
-     * Creates a new verifier for classes under the given directory. The given directory shall be the
-     * root of {@code "*.class"} files. For example if the {@code mypackage.MyClass} class is compiled
-     * in the {@code "MyProject/target/classes/mypackage/MyClass.class"} file, then the root directory
-     * shall be {@code "MyProject/target/classes/"}.
-     *
-     * @param  classRootDirectory   the root of compiled class files.
-     * @param  schemaRootDirectory  if the computer contains a local copy of ISO schemas, path to that directory.
-     *                              Otherwise {@code null}. This is only for making tests faster.
-     */
-    public SchemaCompliance(final Path classRootDirectory, final Path schemaRootDirectory) {
-        this.classRootDirectory  = classRootDirectory;
-        this.schemaRootDirectory = schemaRootDirectory;
-        factory = DocumentBuilderFactory.newInstance();
-        factory.setNamespaceAware(true);
-        buffer = new StringBuilder(100);
-        typeDefinitions = new HashMap<>();
-        schemaLocations = new ArrayDeque<>();
-        allXmlNS = new HashMap<>();
-    }
-
-    /**
-     * Verifies {@link XmlElement} annotations on all {@code *.class} files in the given directory and sub-directories.
-     * The given directory must be a sub-directory of the root directory given at construction time.
-     * This method will invoke itself for scanning sub-directories.
-     *
-     * @param  directory  the directory to scan for classes.
-     * @throws IOException if an error occurred while reading files or schemas.
-     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
-     * @throws ParserConfigurationException if {@link javax.xml.parsers.DocumentBuilder} can not be created.
-     * @throws SAXException if an error occurred while parsing the XSD file.
-     * @throws SchemaException if a XSD file does not comply with our assumptions,
-     *         or a JAXB annotation failed a compliance check.
-     */
-    public void verify(final Path directory)
-            throws IOException, ClassNotFoundException, ParserConfigurationException, SAXException, SchemaException
-    {
-        PackageVerifier verifier = null;
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
-            for (Path path : stream) {
-                final String filename = path.getFileName().toString();
-                if (!filename.startsWith(".")) {
-                    if (Files.isDirectory(path)) {
-                        verify(path);
-                    } else if (filename.endsWith(".class")) {
-                        path = classRootDirectory.relativize(path);
-                        buffer.setLength(0);
-                        buffer.append(path.toString()).setLength(buffer.length() - 6);      // Remove ".class" suffix.
-                        StringBuilders.replace(buffer, '/', '.');
-                        final Class<?> c = Class.forName(buffer.toString());
-                        if (verifier == null) {
-                            verifier = new PackageVerifier(c.getPackage());
-                        }
-                        verifier.verify(c);
-                    }
-                }
-            }
-        } catch (DirectoryIteratorException e) {
-            throw e.getCause();
-        }
-        if (verifier != null) {
-            verifier.reportUnused();
-        }
-    }
-
-    /**
-     * Loads the XSD file at the given URL. Definitions are stored in the {@link #typeDefinitions} map.
-     * Only information of interest are stored, and we assume that the XSD follows OGC/ISO conventions.
-     * This method may be invoked recursively if the XSD contains {@code <xs:include>} elements.
-     *
-     * @param  location  URL to the XSD file to load.
-     */
-    private void loadSchema(final String location)
-            throws IOException, ParserConfigurationException, SAXException, SchemaException
-    {
-        if (!schemaLocations.contains(location)) {
-            final Document doc;
-            try (final InputStream in = new URL(location).openStream()) {
-                doc = factory.newDocumentBuilder().parse(in);
-            }
-            schemaLocations.addLast(location);
-            storeClassDefinition(doc);
-        }
-    }
+    private final Map<String,Boolean> namespaceIsUsed;
 
     /**
-     * Stores information about classes in the given node and children. This method invokes itself
-     * for scanning children, until we reach sub-nodes about properties (in which case we continue
-     * with {@link #storePropertyDefinition(Node)}).
+     * Creates a new verifier for the given package.
      */
-    private void storeClassDefinition(final Node node)
+    PackageVerifier(final SchemaCompliance schemas, final Package pkg)
             throws IOException, ParserConfigurationException, SAXException, SchemaException
     {
-        if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) {
-            switch (node.getNodeName()) {
-                case "schema": {
-                    targetNamespace = getMandatoryAttribute(node, "targetNamespace").intern();
-                    break;
-                }
-                /*
-                 * <xs:include schemaLocation="(…).xsd">
-                 * Load the schema at the given URL, which is assumed relative.
-                 */
-                case "include": {
-                    final String oldTarget = targetNamespace;
-                    final String location = schemaLocations.getLast();
-                    buffer.setLength(0);
-                    buffer.append(location, 0, location.lastIndexOf('/') + 1).append(getMandatoryAttribute(node, "schemaLocation"));
-                    loadSchema(buffer.toString());
-                    targetNamespace = oldTarget;
-                    return;                             // Skip children (normally, there is none).
-                }
-                /*
-                 * <xs:element name="(…)" type="(…)_Type">
-                 * Verify that the names comply with our assumptions.
-                 */
-                case "element": {
-                    final String name = getMandatoryAttribute(node, "name");
-                    final String type = getMandatoryAttribute(node, "type");
-                    if (CODELIST_TYPE.equals(type)) {
-                        final Map<String,Info> properties = new HashMap<>(4);
-                        final Info info = new Info(null, targetNamespace, false, false);
-                        properties.put(null, info);     // Remember namespace of the code list.
-                        properties.put(name, info);     // Pseudo-property used in our CodeList adapters.
-                        if (typeDefinitions.put(name, properties) != null) {
-                            throw new SchemaException("Code list " + name + " defined twice.");
-                        }
-                    } else {
-                        verifyNamingConvention(schemaLocations.getLast(), name, type, TYPE_SUFFIX);
-                        preparePropertyDefinitions(type);
-                        addProperty(null, type, false, false);
-                        currentProperties = null;
-                    }
-                    return;                             // Ignore children (they are about documentation).
-                }
-                /*
-                 * <xs:complexType name="(…)_Type">
-                 * <xs:complexType name="(…)_PropertyType">
-                 */
-                case "complexType": {
-                    String name = getMandatoryAttribute(node, "name");
-                    if (name.endsWith(PROPERTY_TYPE_SUFFIX)) {
-                        currentPropertyType = name;
-                        verifyPropertyType(node);
-                        currentPropertyType = null;
-                    } else {
-                        /*
-                         * In the case of "(…)_PropertyType", replace some ISO 19115-2 types by ISO 19115-1 types.
-                         * For example "MI_Band_Type" is renamed as "MD_Band_Type". We do that because we use only
-                         * one class for representing those two distincts ISO types. Note that not all ISO 19115-2
-                         * types extend an ISO 19115-1 type, so we need to apply a case-by-case approach.
-                         */
-                        name = TYPES_TO_MERGE.getOrDefault(name, name);
-                        preparePropertyDefinitions(name);
-                        storePropertyDefinition(node);
-                        currentProperties = null;
+        this.schemas = schemas;
+        namespaceIsUsed = new HashMap<>();
+        String name = "?", namespace = "";
+        if (pkg != null) {
+            name = pkg.getName();
+            final XmlSchema schema = pkg.getAnnotation(XmlSchema.class);
+            if (schema != null) {
+                namespace = schema.namespace();
+                String location = schema.location();
+                if (!XmlSchema.NO_LOCATION.equals(location)) {
+                    if (!location.startsWith(schema.namespace())) {
+                        throw new SchemaException("XML schema location inconsistent with namespace in package " + name);
                     }
-                    return;                             // Skip children since they have already been examined.
+                    schemas.loadSchema(location);
                 }
-            }
-        }
-        for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
-            storeClassDefinition(child);
-        }
-    }
-
-    /**
-     * Stores information about properties in the current class. The {@link #currentProperties} field must be
-     * set to the map of properties for the class defined by the enclosing {@code <xs:complexType>} element.
-     * This method parses elements of the following form:
-     *
-     * {@preformat xml
-     *   <xs:element name="(…)" type="(…)_PropertyType" minOccurs="(…)" maxOccurs="(…)">
-     * }
-     */
-    private void storePropertyDefinition(final Node node) throws SchemaException {
-        if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) {
-            if ("element".equals(node.getNodeName())) {
-                boolean isRequired   = false;
-                boolean isCollection = false;
-                final NamedNodeMap attributes = node.getAttributes();
-                if (attributes != null) {
-                    Node attr = attributes.getNamedItem("minOccurs");
-                    if (attr != null) {
-                        final String value = attr.getNodeValue();
-                        if (value != null) {
-                            isRequired = Integer.parseInt(getMandatoryAttribute(node, "minOccurs")) > 0;
-                        }
+                for (final XmlNs xmlns : schema.xmlns()) {
+                    final String pr = xmlns.prefix();
+                    final String ns = xmlns.namespaceURI();
+                    final String cr = schemas.allXmlNS.put(pr, ns);
+                    if (cr != null && !cr.equals(ns)) {
+                        throw new SchemaException("Prefix \"" + pr + "\" associated to two different namespaces:\n" + cr + '\n' + ns);
                     }
-                    attr = attributes.getNamedItem("maxOccurs");
-                    if (attr != null) {
-                        final String value = attr.getNodeValue();
-                        if (value != null) {
-                            isCollection = value.equals("unbounded") || Integer.parseInt(value) >  1;
-                        }
+                    if (namespaceIsUsed.put(ns, Boolean.FALSE) != null) {
+                        throw new SchemaException("Duplicated namespace in package " + name + ":\n" + ns);
                     }
                 }
-                addProperty(getMandatoryAttribute(node, "name").intern(),
-                       trim(getMandatoryAttribute(node, "type"), PROPERTY_TYPE_SUFFIX).intern(), isRequired, isCollection);
-                return;
-            }
-        }
-        for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
-            storePropertyDefinition(child);
-        }
-    }
-
-    /**
-     * Verifies the naming convention of property defined by the given node. The {@link #currentPropertyType}
-     * field must be set to the type of the property defined by the enclosing {@code <xs:complexType>} element.
-     * This method parses elements of the following form:
-     *
-     * {@preformat xml
-     *   <xs:element ref="(…)">
-     * }
-     */
-    private void verifyPropertyType(final Node node) throws SchemaException {
-        if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) {
-            if ("element".equals(node.getNodeName())) {
-                verifyNamingConvention(schemaLocations.getLast(),
-                        getMandatoryAttribute(node, "ref"), currentPropertyType, PROPERTY_TYPE_SUFFIX);
-                return;
             }
         }
-        for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
-            verifyPropertyType(child);
-        }
+        packageName = name;
+        defaultNS = namespace;
     }
 
     /**
-     * Verifies that the relationship between the name of the given entity and its type are consistent with
-     * OGC/ISO conventions. This method ignore the prefix (e.g. {@code "mdb:"} in {@code "mdb:MD_Metadata"}).
+     * Verifies {@code @XmlType} and {@code @XmlRootElement} on the class. This method verifies naming convention
+     * (type name should be same as root element name with {@value SchemaCompliance#TYPE_SUFFIX} suffix appended),
+     * ensures that the name exists in the schema, and checks the namespace.
      *
-     * @param  name    the class or property name. Example: {@code "MD_Metadata"}, {@code "citation"}.
-     * @param  type    the type of the above named object. Example: {@code "MD_Metadata_Type"}, {@code "CI_Citation_PropertyType"}.
-     * @param  suffix  the expected suffix at the end of {@code type}.
-     * @throws SchemaException if the given {@code name} and {@code type} are not compliant with expected convention.
+     * @param  type  the class on which to verify annotations.
      */
-    private static void verifyNamingConvention(final String enclosing,
-            final String name, final String type, final String suffix) throws SchemaException
+    final void verify(final Class<?> type)
+            throws IOException, ParserConfigurationException, SAXException, SchemaException
     {
-        if (type.endsWith(suffix)) {
-            int nameStart = name.indexOf(PREFIX_SEPARATOR) + 1;        // Skip "mdb:" or similar prefix.
-            int typeStart = type.indexOf(PREFIX_SEPARATOR) + 1;
-            final int plg = ABSTRACT_PREFIX.length();
-            if (name.regionMatches(nameStart, ABSTRACT_PREFIX, 0, plg)) nameStart += plg;
-            if (type.regionMatches(typeStart, ABSTRACT_PREFIX, 0, plg)) typeStart += plg;
-            final int length = name.length() - nameStart;
-            if (type.length() - typeStart - suffix.length() == length &&
-                    type.regionMatches(typeStart, name, nameStart, length))
-            {
-                return;
-            }
-        }
-        throw new SchemaException("Error in " + enclosing + ":\n"
-                + "The type name should be the name with \"" + suffix + "\" suffix, "
-                + "but found name=\"" + name + "\" and type=\"" + type + "\">.");
-    }
-
-    /**
-     * Adds a property of the current name and type. This method is invoked during schema parsing.
-     * The property namespace is assumed to be {@link #targetNamespace}.
-     */
-    private void addProperty(final String name, final String type, final boolean isRequired, final boolean isCollection) throws SchemaException {
-        final Info info = new Info(type, targetNamespace, isRequired, isCollection);
-        final Info old = currentProperties.put(name, info);
-        if (old != null && !old.equal(info)) {
-            throw new SchemaException("Error while parsing " + schemaLocations.getLast() + ":\n"
-                    + "Property \"" + name + "\" is associated to type \"" + type + "\", but that "
-                    + "property was already associated to \"" + old + "\".");
-        }
-    }
-
-    /**
-     * Removes leading and trailing spaces if any, then the prefix and the suffix in the given name.
-     * The prefix is anything before the first {@value #PREFIX_SEPARATOR} character.
-     * The suffix must be the given string, otherwise an exception is thrown.
-     *
-     * @param  name     the name from which to remove prefix and suffix.
-     * @param  suffix   the suffix to remove.
-     * @return the given name without prefix and suffix.
-     * @throws SchemaException if the given name does not end with the given suffix.
-     */
-    private static String trim(String name, final String suffix) throws SchemaException {
-        name = name.trim();
-        if (name.endsWith(suffix)) {
-            return name.substring(name.indexOf(PREFIX_SEPARATOR) + 1, name.length() - suffix.length());
-        }
-        throw new SchemaException("Expected a name ending with \"" + suffix + "\" but got \"" + name + "\".");
-    }
-
-    /**
-     * Returns the attribute of the given name in the given node,
-     * or throws an exception if the attribute is not present.
-     */
-    private static String getMandatoryAttribute(final Node node, final String name) throws SchemaException {
-        final NamedNodeMap attributes = node.getAttributes();
-        if (attributes != null) {
-            final Node attr = attributes.getNamedItem(name);
-            if (attr != null) {
-                final String value = attr.getNodeValue();
-                if (value != null) {
-                    return value;
-                }
-            }
-        }
-        throw new SchemaException("Node " + node.getNodeName() + " should have a '" + name + "' attribute.");
-    }
-
-    /**
-     * Verify JAXB annotations in a single package.
-     * A new instance of this class must be created for each Java package to be verified.
-     */
-    private final class PackageVerifier {
-        /**
-         * The package name, for reporting error.
-         */
-        private final String packageName;
-
-        /**
-         * The default namespace to use if a class does not define explicitely a namespace.
-         */
-        private final String defaultNS;
-
-        /**
-         * Whether a namespace is actually used of not.
-         * We use this map for identifying unnecessary prefix declarations.
-         */
-        private final Map<String,Boolean> namespaceIsUsed;
-
-        /**
-         * Creates a new verifier for the given package.
-         */
-        PackageVerifier(final Package pkg)
-                throws IOException, ParserConfigurationException, SAXException, SchemaException
-        {
-            namespaceIsUsed = new HashMap<>();
-            String name = "?", namespace = "";
-            if (pkg != null) {
-                name = pkg.getName();
-                final XmlSchema schema = pkg.getAnnotation(XmlSchema.class);
-                if (schema != null) {
-                    namespace = schema.namespace();
-                    String location = schema.location();
-                    if (!XmlSchema.NO_LOCATION.equals(location)) {
-                        if (location.startsWith(SCHEMA_ROOT_DIRECTORY)) {
-                            if (!location.startsWith(schema.namespace())) {
-                                throw new SchemaException("XML schema location inconsistent with namespace in package " + name);
-                            }
-                            if (schemaRootDirectory != null) {
-                                location = schemaRootDirectory.resolve(location.substring(SCHEMA_ROOT_DIRECTORY.length())).toUri().toString();
-                            }
-                        }
-                        loadSchema(location);
-                    }
-                    for (final XmlNs xmlns : schema.xmlns()) {
-                        final String pr = xmlns.prefix();
-                        final String ns = xmlns.namespaceURI();
-                        final String cr = allXmlNS.put(pr, ns);
-                        if (cr != null && !cr.equals(ns)) {
-                            throw new SchemaException("Prefix \"" + pr + "\" associated to two different namespaces:\n" + cr + '\n' + ns);
-                        }
-                        if (namespaceIsUsed.put(ns, Boolean.FALSE) != null) {
-                            throw new SchemaException("Duplicated namespace in package " + name + ":\n" + ns);
-                        }
-                    }
-                }
-            }
-            packageName = name;
-            defaultNS = namespace;
-        }
-
-        /**
-         * Verifies {@code @XmlType} and {@code @XmlRootElement} on the class. This method verifies naming convention
-         * (type name should be same as root element name with {@value #TYPE_SUFFIX} suffix appended), ensures that
-         * the name exists in the schema, and checks the namespace.
-         *
-         * @param  type  the class on which to verify annotations.
-         */
-        final void verify(final Class<?> type)
-                throws IOException, ParserConfigurationException, SAXException, SchemaException
-        {
-            final XmlType        xmlType = type.getDeclaredAnnotation(XmlType.class);
-            final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class);
-            if (xmlRoot == null && xmlType == null) {
-                return;
-            }
-            /*
-             * Get the type name and namespace from the @XmlType or @XmlRootElement annotations.
-             * If both of them are present, verify that they are consistent (same namespace and
-             * same name with "_Type" suffix in @XmlType). If the type name is not declared, we
-             * assume that it is the same than the class name (this is what Apache SIS 0.8 does
-             * in its org.apache.sis.internal.jaxb.code package for CodeList adapters).
-             */
-            String namespace;
-            final String className;     // ISO class name (not the same than Java class name).
-            if (xmlRoot != null) {
-                namespace = xmlRoot.namespace();
-                className = xmlRoot.name();
-                if (xmlType != null) {
-                    if (!namespace.equals(xmlType.namespace())) {
-                        throw new SchemaException("Mismatched namespace in @XmlType and @XmlRootElement of " + type);
-                    }
-                    verifyNamingConvention(type.getName(), className, xmlType.name(), TYPE_SUFFIX);
-                }
-            } else {
-                namespace = xmlType.namespace();
-                final String name = xmlType.name();
-                className = name.equals("##default") ? type.getSimpleName() : trim(name, TYPE_SUFFIX);
+        final XmlType        xmlType = type.getDeclaredAnnotation(XmlType.class);
+        final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class);
+        if (xmlRoot == null && xmlType == null) {
+            return;
+        }
+        /*
+         * Get the type name and namespace from the @XmlType or @XmlRootElement annotations.
+         * If both of them are present, verify that they are consistent (same namespace and
+         * same name with "_Type" suffix in @XmlType). If the type name is not declared, we
+         * assume that it is the same than the class name (this is what Apache SIS 0.8 does
+         * in its org.apache.sis.internal.jaxb.code package for CodeList adapters).
+         */
+        String namespace;
+        final String className;     // ISO class name (not the same than Java class name).
+        if (xmlRoot != null) {
+            namespace = xmlRoot.namespace();
+            className = xmlRoot.name();
+            if (xmlType != null) {
+                if (!namespace.equals(xmlType.namespace())) {
+                    throw new SchemaException("Mismatched namespace in @XmlType and @XmlRootElement of " + type);
+                }
+                SchemaCompliance.verifyNamingConvention(type.getName(), className, xmlType.name(), SchemaCompliance.TYPE_SUFFIX);
+            }
+        } else {
+            namespace = xmlType.namespace();
+            final String name = xmlType.name();
+            className = name.equals(AnnotationConsistencyCheck.DEFAULT)
+                        ? type.getSimpleName() : SchemaCompliance.trim(name, SchemaCompliance.TYPE_SUFFIX);
+        }
+        /*
+         * Verify that the namespace declared on the class is not redundant with the namespace
+         * declared in the package. Actually redundant namespaces are not wrong, but we try to
+         * reduce code size.
+         */
+        if (namespace.equals(AnnotationConsistencyCheck.DEFAULT)) {
+            namespace = defaultNS;
+        } else if (namespace.equals(defaultNS)) {
+            throw new SchemaException("Redundant namespace declaration in " + type);
+        }
+        /*
+         * Verify that the namespace has a prefix associated to it in the package-info file.
+         */
+        if (namespaceIsUsed.put(namespace, Boolean.TRUE) == null) {
+            throw new SchemaException("Namespace of " + type + " has no prefix in package-info.");
+        }
+        /*
+         * Properties in the legacy GMD or GMI namespaces may be deprecated, depending if a replacement
+         * is already available or not. However properties in other namespaces should not be deprecated.
+         * Some validations will be disabled for deprecated properties.
+         */
+        boolean isDeprecated = DEPRECATED_NAMESPACES.contains(namespace);
+        if (!isDeprecated && type.isAnnotationPresent(Deprecated.class)) {
+            throw new SchemaException("Unexpected deprecation status of " + type);
+        }
+        final Map<String, SchemaCompliance.Info> properties = schemas.typeDefinition(className);
+        if (properties == null) {
+            if (!isDeprecated) {
+                throw new SchemaException("Unknown name declared in @XmlRootElement of " + type);
             }
+        } else {
             /*
-             * Verify that the namespace declared on the class is not redundant with the namespace
-             * declared in the package. Actually redundant namespaces are not wrong, but we try to
-             * reduce code size.
+             * Verify the class namespace (associated to the null key by convention),
+             * then verify @XmlElement annotation on each property.
              */
-            if (namespace.equals("##default")) {
-                namespace = defaultNS;
-            } else if (namespace.equals(defaultNS)) {
-                throw new SchemaException("Redundant namespace declaration in " + type);
+            final String expectedNS = properties.get(null).namespace;
+            if (!namespace.equals(expectedNS)) {
+                throw new SchemaException(className + " shall be associated to namespace " + expectedNS);
             }
-            /*
-             * Verify that the namespace has a prefix associated to it in the package-info file.
-             */
-            if (namespaceIsUsed.put(namespace, Boolean.TRUE) == null) {
-                throw new SchemaException("Namespace of " + type + " has no prefix in package-info.");
-            }
-            /*
-             * Properties in the legacy GMD or GMI namespaces may be deprecated, depending if a replacement
-             * is already available or not. However properties in other namespaces should not be deprecated.
-             * Some validations will be disabled for deprecated properties.
-             */
-            boolean isDeprecated = DEPRECATED_NAMESPACES.contains(namespace);
-            if (!isDeprecated && type.isAnnotationPresent(Deprecated.class)) {
-                throw new SchemaException("Unexpected deprecation status of " + type);
-            }
-            final Map<String,Info> properties = typeDefinitions.get(className);
-            if (properties == null) {
-                if (!isDeprecated) {
-                    throw new SchemaException("Unknown name declared in @XmlRootElement of " + type);
-                }
-            } else {
-                /*
-                 * Verify the class namespace (associated to the null key by convention),
-                 * then verify @XmlElement annotation on each property.
-                 */
-                final String expectedNS = properties.get(null).namespace;
-                if (!namespace.equals(expectedNS)) {
-                    throw new SchemaException(className + " shall be associated to namespace " + expectedNS);
-                }
-                for (final Method method : type.getDeclaredMethods()) {
-                    final XmlElement element = method.getDeclaredAnnotation(XmlElement.class);
-                    if (element != null) {
-                        final String name = element.name();
-                        final String ns = element.namespace();
-                        isDeprecated = DEPRECATED_NAMESPACES.contains(ns);
-                        if (!isDeprecated && method.isAnnotationPresent(Deprecated.class)) {
-                            throw new SchemaException("Unexpected deprecation status of " + className + '.' + name);
-                        }
-                        final Info info = properties.get(name);
-                        if (info == null) {
-                            if (!isDeprecated) {
-                                throw new SchemaException("Unexpected XML element " + className + '.' + name);
-                            }
+            for (final Method method : type.getDeclaredMethods()) {
+                final XmlElement element = method.getDeclaredAnnotation(XmlElement.class);
+                if (element != null) {
+                    final String name = element.name();
+                    final String ns = element.namespace();
+                    isDeprecated = DEPRECATED_NAMESPACES.contains(ns);
+                    if (!isDeprecated && method.isAnnotationPresent(Deprecated.class)) {
+                        throw new SchemaException("Unexpected deprecation status of " + className + '.' + name);
+                    }
+                    final SchemaCompliance.Info info = properties.get(name);
+                    if (info == null) {
+                        if (!isDeprecated) {
+                            throw new SchemaException("Unexpected XML element " + className + '.' + name);
                         }
                     }
                 }
             }
         }
+    }
 
-        /**
-         * Verifies if there is any unused namespace or adapters in package-info file.
-         */
-        final void reportUnused() throws SchemaException {
-            for (final Map.Entry<String,Boolean> entry : namespaceIsUsed.entrySet()) {
-                if (!entry.getValue()) {
-//                  TODO: to be enabled after we processed properties.
-//                  throw new SchemaException("Unused namespace in package " + packageName + ":\n" + entry.getKey());
-                }
+    /**
+     * Verifies if there is any unused namespace or adapters in package-info file.
+     */
+    final void reportUnused() throws SchemaException {
+        for (final Map.Entry<String,Boolean> entry : namespaceIsUsed.entrySet()) {
+            if (!entry.getValue()) {
+//              TODO: to be enabled after we processed properties.
+//              throw new SchemaException("Unused namespace in package " + packageName + ":\n" + entry.getKey());
             }
         }
     }

Modified: sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java?rev=1822963&r1=1822962&r2=1822963&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java [UTF-8] Fri Feb  2 14:01:31 2018
@@ -24,21 +24,14 @@ import java.nio.file.Files;
 import java.nio.file.DirectoryStream;
 import java.nio.file.DirectoryIteratorException;
 import java.util.Map;
-import java.util.Set;
 import java.util.Deque;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.ArrayDeque;
-import java.util.Arrays;
 import java.util.Objects;
 import java.util.Collections;
-import java.lang.reflect.Method;
 import javax.xml.XMLConstants;
 import javax.xml.bind.annotation.XmlNs;
-import javax.xml.bind.annotation.XmlType;
-import javax.xml.bind.annotation.XmlSchema;
 import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 import org.w3c.dom.Node;
@@ -46,7 +39,6 @@ import org.w3c.dom.Document;
 import org.w3c.dom.NamedNodeMap;
 import org.xml.sax.SAXException;
 import org.apache.sis.util.StringBuilders;
-import org.apache.sis.internal.jaxb.LegacyNamespaces;
 
 
 /**
@@ -79,12 +71,6 @@ public final strictfp class SchemaCompli
     private static final String SCHEMA_ROOT_DIRECTORY = "http://standards.iso.org/iso/";
 
     /**
-     * Classes or properties having a JAXB annotation in this namespace should be deprecated.
-     */
-    private static final Set<String> DEPRECATED_NAMESPACES = Collections.unmodifiableSet(new HashSet<>(
-            Arrays.asList(LegacyNamespaces.GMD, LegacyNamespaces.GMI, LegacyNamespaces.SRV)));
-
-    /**
      * ISO 19115-2 classes to merge with ISO 19115-1 classes. For example ISO 19115-2 defines {@code MI_Band}
      * as an extension of ISO 19115-1 {@code MD_Band}, but GeoAPI and Apache SIS merges those two types in a
      * single class for simplicity. Consequently when reading the schema, we rename some {@code MI_*} types
@@ -114,7 +100,7 @@ public final strictfp class SchemaCompli
      * The suffix of XML type names for classes.
      * This is used by convention in OGC/ISO standards (but not necessarily in other XSD).
      */
-    private static final String TYPE_SUFFIX = "_Type";
+    static final String TYPE_SUFFIX = "_Type";
 
     /**
      * The suffix of XML property type names in a given class.
@@ -167,7 +153,7 @@ public final strictfp class SchemaCompli
     /**
      * The type and namespace of a property or class. Used in {@link #typeDefinitions} map.
      */
-    private static final class Info {
+    static final class Info {
         final String  typeName;
         final String  namespace;
         final boolean isRequired;
@@ -230,8 +216,11 @@ public final strictfp class SchemaCompli
     /**
      * The namespaces associated to prefixes, as declared by JAXB {@link XmlNs} annotations.
      * Used for verifying that no prefix is defined twice for different namespaces.
+     *
+     * <p>This field is not really related to schema loading process. But we keep it in this class for
+     * {@link PackageVerifier} convenience, as a way to share a single map for all verifier instances.</p>
      */
-    private final Map<String,String> allXmlNS;
+    final Map<String,String> allXmlNS;
 
     /**
      * Creates a new verifier for classes under the given directory. The given directory shall be the
@@ -284,7 +273,7 @@ public final strictfp class SchemaCompli
                         StringBuilders.replace(buffer, '/', '.');
                         final Class<?> c = Class.forName(buffer.toString());
                         if (verifier == null) {
-                            verifier = new PackageVerifier(c.getPackage());
+                            verifier = new PackageVerifier(this, c.getPackage());
                         }
                         verifier.verify(c);
                     }
@@ -305,9 +294,12 @@ public final strictfp class SchemaCompli
      *
      * @param  location  URL to the XSD file to load.
      */
-    private void loadSchema(final String location)
+    final void loadSchema(String location)
             throws IOException, ParserConfigurationException, SAXException, SchemaException
     {
+        if (schemaRootDirectory != null && location.startsWith(SCHEMA_ROOT_DIRECTORY)) {
+            location = schemaRootDirectory.resolve(location.substring(SCHEMA_ROOT_DIRECTORY.length())).toUri().toString();
+        }
         if (!schemaLocations.contains(location)) {
             final Document doc;
             try (final InputStream in = new URL(location).openStream()) {
@@ -471,7 +463,7 @@ public final strictfp class SchemaCompli
      * @param  suffix  the expected suffix at the end of {@code type}.
      * @throws SchemaException if the given {@code name} and {@code type} are not compliant with expected convention.
      */
-    private static void verifyNamingConvention(final String enclosing,
+    static void verifyNamingConvention(final String enclosing,
             final String name, final String type, final String suffix) throws SchemaException
     {
         if (type.endsWith(suffix)) {
@@ -516,7 +508,7 @@ public final strictfp class SchemaCompli
      * @return the given name without prefix and suffix.
      * @throws SchemaException if the given name does not end with the given suffix.
      */
-    private static String trim(String name, final String suffix) throws SchemaException {
+    static String trim(String name, final String suffix) throws SchemaException {
         name = name.trim();
         if (name.endsWith(suffix)) {
             return name.substring(name.indexOf(PREFIX_SEPARATOR) + 1, name.length() - suffix.length());
@@ -543,175 +535,11 @@ public final strictfp class SchemaCompli
     }
 
     /**
-     * Verify JAXB annotations in a single package.
-     * A new instance of this class must be created for each Java package to be verified.
+     * Returns the type definitions for a class of the given name.
+     *
+     * @param  className  ISO identifier of a class (e.g. {@code "MD_Metadata"}).
      */
-    private final class PackageVerifier {
-        /**
-         * The package name, for reporting error.
-         */
-        private final String packageName;
-
-        /**
-         * The default namespace to use if a class does not define explicitely a namespace.
-         */
-        private final String defaultNS;
-
-        /**
-         * Whether a namespace is actually used of not.
-         * We use this map for identifying unnecessary prefix declarations.
-         */
-        private final Map<String,Boolean> namespaceIsUsed;
-
-        /**
-         * Creates a new verifier for the given package.
-         */
-        PackageVerifier(final Package pkg)
-                throws IOException, ParserConfigurationException, SAXException, SchemaException
-        {
-            namespaceIsUsed = new HashMap<>();
-            String name = "?", namespace = "";
-            if (pkg != null) {
-                name = pkg.getName();
-                final XmlSchema schema = pkg.getAnnotation(XmlSchema.class);
-                if (schema != null) {
-                    namespace = schema.namespace();
-                    String location = schema.location();
-                    if (!XmlSchema.NO_LOCATION.equals(location)) {
-                        if (location.startsWith(SCHEMA_ROOT_DIRECTORY)) {
-                            if (!location.startsWith(schema.namespace())) {
-                                throw new SchemaException("XML schema location inconsistent with namespace in package " + name);
-                            }
-                            if (schemaRootDirectory != null) {
-                                location = schemaRootDirectory.resolve(location.substring(SCHEMA_ROOT_DIRECTORY.length())).toUri().toString();
-                            }
-                        }
-                        loadSchema(location);
-                    }
-                    for (final XmlNs xmlns : schema.xmlns()) {
-                        final String pr = xmlns.prefix();
-                        final String ns = xmlns.namespaceURI();
-                        final String cr = allXmlNS.put(pr, ns);
-                        if (cr != null && !cr.equals(ns)) {
-                            throw new SchemaException("Prefix \"" + pr + "\" associated to two different namespaces:\n" + cr + '\n' + ns);
-                        }
-                        if (namespaceIsUsed.put(ns, Boolean.FALSE) != null) {
-                            throw new SchemaException("Duplicated namespace in package " + name + ":\n" + ns);
-                        }
-                    }
-                }
-            }
-            packageName = name;
-            defaultNS = namespace;
-        }
-
-        /**
-         * Verifies {@code @XmlType} and {@code @XmlRootElement} on the class. This method verifies naming convention
-         * (type name should be same as root element name with {@value #TYPE_SUFFIX} suffix appended), ensures that
-         * the name exists in the schema, and checks the namespace.
-         *
-         * @param  type  the class on which to verify annotations.
-         */
-        final void verify(final Class<?> type)
-                throws IOException, ParserConfigurationException, SAXException, SchemaException
-        {
-            final XmlType        xmlType = type.getDeclaredAnnotation(XmlType.class);
-            final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class);
-            if (xmlRoot == null && xmlType == null) {
-                return;
-            }
-            /*
-             * Get the type name and namespace from the @XmlType or @XmlRootElement annotations.
-             * If both of them are present, verify that they are consistent (same namespace and
-             * same name with "_Type" suffix in @XmlType). If the type name is not declared, we
-             * assume that it is the same than the class name (this is what Apache SIS 0.8 does
-             * in its org.apache.sis.internal.jaxb.code package for CodeList adapters).
-             */
-            String namespace;
-            final String className;     // ISO class name (not the same than Java class name).
-            if (xmlRoot != null) {
-                namespace = xmlRoot.namespace();
-                className = xmlRoot.name();
-                if (xmlType != null) {
-                    if (!namespace.equals(xmlType.namespace())) {
-                        throw new SchemaException("Mismatched namespace in @XmlType and @XmlRootElement of " + type);
-                    }
-                    verifyNamingConvention(type.getName(), className, xmlType.name(), TYPE_SUFFIX);
-                }
-            } else {
-                namespace = xmlType.namespace();
-                final String name = xmlType.name();
-                className = name.equals("##default") ? type.getSimpleName() : trim(name, TYPE_SUFFIX);
-            }
-            /*
-             * Verify that the namespace declared on the class is not redundant with the namespace
-             * declared in the package. Actually redundant namespaces are not wrong, but we try to
-             * reduce code size.
-             */
-            if (namespace.equals("##default")) {
-                namespace = defaultNS;
-            } else if (namespace.equals(defaultNS)) {
-                throw new SchemaException("Redundant namespace declaration in " + type);
-            }
-            /*
-             * Verify that the namespace has a prefix associated to it in the package-info file.
-             */
-            if (namespaceIsUsed.put(namespace, Boolean.TRUE) == null) {
-                throw new SchemaException("Namespace of " + type + " has no prefix in package-info.");
-            }
-            /*
-             * Properties in the legacy GMD or GMI namespaces may be deprecated, depending if a replacement
-             * is already available or not. However properties in other namespaces should not be deprecated.
-             * Some validations will be disabled for deprecated properties.
-             */
-            boolean isDeprecated = DEPRECATED_NAMESPACES.contains(namespace);
-            if (!isDeprecated && type.isAnnotationPresent(Deprecated.class)) {
-                throw new SchemaException("Unexpected deprecation status of " + type);
-            }
-            final Map<String,Info> properties = typeDefinitions.get(className);
-            if (properties == null) {
-                if (!isDeprecated) {
-                    throw new SchemaException("Unknown name declared in @XmlRootElement of " + type);
-                }
-            } else {
-                /*
-                 * Verify the class namespace (associated to the null key by convention),
-                 * then verify @XmlElement annotation on each property.
-                 */
-                final String expectedNS = properties.get(null).namespace;
-                if (!namespace.equals(expectedNS)) {
-                    throw new SchemaException(className + " shall be associated to namespace " + expectedNS);
-                }
-                for (final Method method : type.getDeclaredMethods()) {
-                    final XmlElement element = method.getDeclaredAnnotation(XmlElement.class);
-                    if (element != null) {
-                        final String name = element.name();
-                        final String ns = element.namespace();
-                        isDeprecated = DEPRECATED_NAMESPACES.contains(ns);
-                        if (!isDeprecated && method.isAnnotationPresent(Deprecated.class)) {
-                            throw new SchemaException("Unexpected deprecation status of " + className + '.' + name);
-                        }
-                        final Info info = properties.get(name);
-                        if (info == null) {
-                            if (!isDeprecated) {
-                                throw new SchemaException("Unexpected XML element " + className + '.' + name);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        /**
-         * Verifies if there is any unused namespace or adapters in package-info file.
-         */
-        final void reportUnused() throws SchemaException {
-            for (final Map.Entry<String,Boolean> entry : namespaceIsUsed.entrySet()) {
-                if (!entry.getValue()) {
-//                  TODO: to be enabled after we processed properties.
-//                  throw new SchemaException("Unused namespace in package " + packageName + ":\n" + entry.getKey());
-                }
-            }
-        }
+    final Map<String, SchemaCompliance.Info> typeDefinition(final String className) {
+        return typeDefinitions.get(className);
     }
 }



Mime
View raw message