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);
}
}
|