sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1470608 - in /sis/branches/JDK7: sis-metadata/src/main/java/org/apache/sis/metadata/ sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/ sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/ sis-metadata/src/main/j...
Date Mon, 22 Apr 2013 16:32:37 GMT
Author: desruisseaux
Date: Mon Apr 22 16:32:37 2013
New Revision: 1470608

URL: http://svn.apache.org/r1470608
Log:
Ported more tests.

Added:
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/auto-properties.html   (with props)
    sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java   (with props)
    sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java   (with props)
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java   (with props)
Modified:
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
    sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
    sis/branches/JDK7/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java

Modified: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java?rev=1470608&r1=1470607&r2=1470608&view=diff
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -124,7 +124,8 @@ final class PropertyAccessor {
     final Class<?> type;
 
     /**
-     * The implementation class. The following condition must hold:
+     * The implementation class, or {@link #type} if none.
+     * The following condition must hold:
      *
      * {@preformat java
      *     type.isAssignableFrom(implementation);
@@ -218,7 +219,7 @@ final class PropertyAccessor {
      * @param  standard The standard which define the {@code type} interface.
      * @param  type The interface implemented by the metadata, which must be
      *         the value returned by {@link #getStandardType(Class, String)}.
-     * @param  implementation The class of metadata implementations.
+     * @param  implementation The class of metadata implementations, or {@code type} if none.
      */
     PropertyAccessor(final Citation standard, final Class<?> type, final Class<?> implementation) {
         assert type.isAssignableFrom(implementation) : implementation;
@@ -590,6 +591,13 @@ final class PropertyAccessor {
     }
 
     /**
+     * Returns {@code true} if the property at the given index is writable.
+     */
+    final boolean isWritable(final int index) {
+        return (index >= 0) && (index < standardCount) && (setters != null) && (setters[index] != null);
+    }
+
+    /**
      * Returns the value for the specified metadata, or {@code null} if none.
      * If the given index is out of bounds, then this method returns {@code null},
      * so it is safe to invoke this method even if {@link #indexOf(String, boolean)}

Modified: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java?rev=1470608&r1=1470607&r2=1470608&view=diff
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -222,7 +222,7 @@ public class DefaultOnlineResource exten
 
     /**
      * Returns the location (address) for on-line access using a Uniform Resource Locator address or
-     * similar addressing scheme such as @{@code http://www.statkart.no/isotc211}".
+     * similar addressing scheme such as "{@code http://www.statkart.no/isotc211}".
      */
     @Override
     @XmlElement(name = "linkage", required = true)
@@ -242,11 +242,16 @@ public class DefaultOnlineResource exten
     }
 
     /**
-     * Returns the connection protocol to be used.
+     * Returns the connection protocol to be used. If no protocol has been {@linkplain #setProtocol(String)
+     * explicitely set}, then this method returns the {@linkplain #getLinkage() linkage}
+     * {@linkplain URI#getScheme() scheme} (if any).
      */
     @Override
     @XmlElement(name = "protocol")
     public String getProtocol() {
+        if (protocol == null && linkage != null) {
+            return linkage.getScheme();
+        }
         return protocol;
     }
 

Added: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/auto-properties.html
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/auto-properties.html?rev=1470608&view=auto
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/auto-properties.html (added)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/auto-properties.html Mon Apr 22 16:32:37 2013
@@ -0,0 +1,16 @@
+<html>
+  <head>
+    <title>List of auto-generated metadata properties</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  </head>
+  <body>
+    <h1>List of auto-generated metadata properties</h1>
+    <p>If any of the following properties is not explicitely defined by a call to the corresponding setter method,
+    then the Apache SIS default implementations will infer a default value as described below:</p>
+
+    <ul>
+      <li><code><a href="../citation/DefaultOnlineResource#getProtocol()">CI_OnlineResource.protocol</a></code>
+      defaults to the schema part of the linkage URI.</li>
+    </ul>
+  </body>
+</html>

Propchange: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/auto-properties.html
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/auto-properties.html
------------------------------------------------------------------------------
    svn:mime-type = text/html

Modified: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java?rev=1470608&r1=1470607&r2=1470608&view=diff
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -218,6 +218,8 @@ public class DefaultScopeDescription ext
             c = cast(value, type);
         } else if (isNullOrEmpty(newValue)) {
             return;
+        } else {
+            property = code;
         }
         value = writeSet(newValue, c, type);
     }
@@ -343,6 +345,7 @@ public class DefaultScopeDescription ext
     public void setDataset(final String newValue) {
         checkWritePermission();
         if (newValue != null || property == DATASET) {
+            property = DATASET;
             value = newValue;
         }
     }
@@ -370,6 +373,7 @@ public class DefaultScopeDescription ext
     public void setOther(final String newValue) {
         checkWritePermission();
         if (newValue != null || property == OTHER) {
+            property = OTHER;
             value = newValue;
         }
     }

Added: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java?rev=1470608&view=auto
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java (added)
+++ sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.metadata;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.Random;
+import java.util.Collection;
+import org.opengis.util.CodeList;
+import org.apache.sis.util.Numbers;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.collection.CheckedContainer;
+import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.test.AnnotationsTestCase;
+import org.apache.sis.test.TestUtilities;
+import org.apache.sis.test.DependsOn;
+import org.junit.Test;
+
+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:
+ *
+ * <ul>
+ *   <li>All {@link AbstractMetadata} instance shall be initially {@linkplain AbstractMetadata#isEmpty() empty}.</li>
+ *   <li>All getter methods shall returns a null singleton or an empty collection, never a null collection.</li>
+ *   <li>After a call to a setter method, the getter method shall return a value equals to the given value.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-2.4)
+ * @version 0.3
+ * @module
+ */
+@DependsOn(PropertyAccessorTest.class)
+public abstract strictfp class MetadataTestCase extends AnnotationsTestCase {
+    /**
+     * The standard implemented by the metadata objects to test.
+     */
+    private final MetadataStandard standard;
+
+    /**
+     * Random generator, or {@code null} if not needed.
+     */
+    private Random random;
+
+    /**
+     * Creates a new test suite for the given types.
+     *
+     * @param standard The standard implemented by the metadata objects to test.
+     * @param types The GeoAPI interfaces or {@link CodeList} types to test.
+     */
+    protected MetadataTestCase(final MetadataStandard standard, final Class<?>... types) {
+        super(types);
+        this.standard = standard;
+    }
+
+    /**
+     * Returns the SIS implementation for the given GeoAPI interface.
+     */
+    @Override
+    protected <T> Class<? extends T> getImplementation(final Class<T> type) {
+        assertTrue(standard.isMetadata(type));
+        return standard.getImplementation(type).asSubclass(type);
+    }
+
+    /**
+     * Return {@code true} if the given property is expected to be writable.
+     * If this method returns {@code false}, then {@link #testPropertyValues()}
+     * will not try to write a value for that property.
+     *
+     * <p>The default implementation returns {@code true}.</p>
+     *
+     * @param  impl     The implementation class.
+     * @param  property The name of the property to test.
+     * @return {@code true} if the given property is writable.
+     */
+    protected boolean isWritable(final Class<?> impl, final String property) {
+        return true;
+    }
+
+    /**
+     * Returns a dummy value of the given type. The default implementation returns values for
+     * {@link CharSequence}, {@link Number}, {@link Date}, {@link Locale}, {@link CodeList}
+     * and types in the {@link #types} list.
+     *
+     * <p>The returned value may be of an other type than the given one if the
+     * {@code PropertyAccessor} converter method know how to convert that type.</p>
+     *
+     * @param  property The name of the property for which to create a value.
+     * @param  type The type of value to create.
+     * @return The value of the given {@code type}, or of a type convertible to the given type.
+     */
+    protected Object valueFor(final String property, final Class<?> type) {
+        if (CharSequence.class.isAssignableFrom(type)) {
+            return "Dummy value for " + property + '.';
+        }
+        switch (Numbers.getEnumConstant(type)) {
+            case Numbers.DOUBLE:  return         random.nextDouble() * 100;
+            case Numbers.FLOAT:   return         random.nextFloat()  * 100f;
+            case Numbers.LONG:    return (long)  random.nextInt(1000000) + 1;
+            case Numbers.INTEGER: return         random.nextInt(  10000) + 1;
+            case Numbers.SHORT:   return (short) random.nextInt(   1000) + 1;
+            case Numbers.BYTE:    return (byte)  random.nextInt(    100) + 1;
+            case Numbers.BOOLEAN: return         random.nextBoolean();
+        }
+        if (Date.class.isAssignableFrom(type)) {
+            return new Date(random.nextInt() * 1000L);
+        }
+        if (CodeList.class.isAssignableFrom(type)) try {
+            final CodeList[] codes = (CodeList[]) type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
+            return codes[random.nextInt(codes.length)];
+        } catch (ReflectiveOperationException e) {
+            fail(e.toString());
+        }
+        if (Locale.class.isAssignableFrom(type)) {
+            switch (random.nextInt(4)) {
+                case 0: return Locale.ROOT;
+                case 1: return Locale.ENGLISH;
+                case 2: return Locale.FRENCH;
+                case 3: return Locale.JAPANESE;
+            }
+        }
+        if (ArraysExt.containsIdentity(types, type)) {
+            final Class<?> impl = getImplementation(type);
+            if (impl != null) try {
+                return impl.getConstructor((Class<?>[]) null).newInstance((Object[]) null);
+            } catch (ReflectiveOperationException e) {
+                fail(e.toString());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Normalizes the type of the given value before comparison. In particular, {@code InternationalString}
+     * instances are converted to {@code String} instances.
+     */
+    private static Object normalizeType(Object value) {
+        if (value instanceof CharSequence) {
+            value = value.toString();
+        }
+        return value;
+    }
+
+    /**
+     * Validates the given newly constructed metadata. The default implementation ensures that
+     * {@link AbstractMetadata.isEmpty()} returns {@code true}.
+     *
+     * @param metadata The metadata to validate.
+     */
+    protected void validate(final AbstractMetadata metadata) {
+        assertTrue("AbstractMetadata.isEmpty()", metadata.isEmpty());
+    }
+
+    /**
+     * For every properties in every non-{@code Codelist} types listed in the {@link #types} array,
+     * test the property values. This method performs the tests documented in class javadoc.
+     */
+    @Test
+    public void testPropertyValues() {
+        random = TestUtilities.createRandomNumberGenerator("testPropertyValues");
+        for (final Class<?> type : types) {
+            if (!CodeList.class.isAssignableFrom(type)) {
+                final Class<?> impl = getImplementation(type);
+                if (impl != null) {
+                    assertTrue(type.isAssignableFrom(impl));
+                    testPropertyValues(new PropertyAccessor(standard.getCitation(), type, impl));
+                }
+            }
+        }
+    }
+
+    /**
+     * Implementation of {@link #testPropertyValues()} for a single class.
+     */
+    private void testPropertyValues(final PropertyAccessor accessor) {
+        /*
+         * Try to instantiate the implementation. Every implementation should
+         * have a no-args constructor, and their instantiation should never fail.
+         */
+        testingClass = accessor.implementation.getCanonicalName();
+        final Object instance;
+        try {
+            instance = accessor.implementation.getConstructor((Class<?>[]) null).newInstance((Object[]) null);
+        } catch (ReflectiveOperationException e) {
+            fail(e.toString());
+            return;
+        }
+        if (instance instanceof AbstractMetadata) {
+            validate((AbstractMetadata) instance);
+        }
+        /*
+         * Iterate over all properties defined in the interface,
+         * and checks for the existences of a setter method.
+         */
+        final int count = accessor.count();
+        for (int i=0; i<count; i++) {
+            testingMethod = accessor.name(i, KeyNamePolicy.METHOD_NAME);
+            final String property = accessor.name(i, KeyNamePolicy.JAVABEANS_PROPERTY);
+            assertNotNull("Missing method name.", testingMethod);
+            assertNotNull("Missing property name.", property);
+            assertEquals("Wrong property index.", i, accessor.indexOf(property, true));
+            /*
+             * Get the property type. In the special case where the property type
+             * is a collection, this is the type of elements in that collection.
+             */
+            final Class<?> propertyType = Numbers.primitiveToWrapper(accessor.type(i, TypeValuePolicy.PROPERTY_TYPE));
+            final Class<?>  elementType = Numbers.primitiveToWrapper(accessor.type(i, TypeValuePolicy.ELEMENT_TYPE));
+            final boolean isCollection = Collection.class.isAssignableFrom(propertyType);
+            assertFalse("Element type can not be Collection.", Collection.class.isAssignableFrom(elementType));
+            assertEquals("Property and element types shall be the same if and only if not a collection.",
+                         !isCollection, propertyType == elementType);
+            /*
+             * Try to get a value.
+             */
+            Object value = accessor.get(i, instance);
+            if (value == null) {
+                assertFalse("Null values are not allowed to be collections.", isCollection);
+            } else {
+                assertInstanceOf("Wrong property type.", propertyType, value);
+                if (value instanceof CheckedContainer<?>) {
+                    assertTrue("Wrong element type in collection.",
+                            elementType.isAssignableFrom(((CheckedContainer<?>) value).getElementType()));
+                }
+                if (isCollection) {
+                    assertTrue("Collections shall be initially empty.", ((Collection<?>) value).isEmpty());
+                    value = CollectionsExt.modifiableCopy((Collection<?>) value); // Protect from changes.
+                }
+            }
+            /*
+             * Try to write a value.
+             */
+            if (isWritable(accessor.implementation, property)) {
+                assertTrue(property, accessor.isWritable(i));
+                final Object newValue = valueFor(property, elementType);
+                final Object oldValue = accessor.set(i, instance, newValue, true);
+                assertEquals("PropertyAccessor.set(…) shall return the value previously returned by get(…).", value, oldValue);
+                value = accessor.get(i, instance);
+                if (isCollection) {
+                    if (newValue == null) {
+                        assertTrue("We didn't generated a random value for this type, consequently the "
+                                + "collection should still empty.", ((Collection<?>) value).isEmpty());
+                        value = null;
+                    } else {
+                        value = TestUtilities.getSingleton((Collection<?>) value);
+                    }
+                }
+                assertEquals("PropertyAccessor.get(…) shall return the value that we have just set.",
+                        normalizeType(newValue), normalizeType(value));
+            }
+        }
+    }
+}

Propchange: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java?rev=1470608&view=auto
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java (added)
+++ sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.metadata.iso;
+
+import java.lang.reflect.Modifier;
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import org.opengis.annotation.Specification;
+import org.apache.sis.metadata.MetadataStandard;
+import org.apache.sis.metadata.MetadataTestCase;
+import org.apache.sis.metadata.iso.quality.AbstractElement;
+import org.apache.sis.metadata.iso.identification.DefaultRepresentativeFraction;
+import org.apache.sis.xml.Namespaces;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests all known {@link ISOMetadata} subclasses for JAXB annotations and getter/setter methods.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-2.4)
+ * @version 0.3
+ * @module
+ */
+public final strictfp class AllMetadataTest extends MetadataTestCase {
+    /**
+     * Creates a new test case with all GeoAPI interfaces and code lists to test.
+     */
+    public AllMetadataTest() {
+        super(MetadataStandard.ISO_19115,
+            org.opengis.metadata.ApplicationSchemaInformation.class,
+            org.opengis.metadata.Datatype.class,
+            org.opengis.metadata.ExtendedElementInformation.class,
+            org.opengis.metadata.FeatureTypeList.class,
+            org.opengis.metadata.Identifier.class,
+            org.opengis.metadata.Metadata.class,
+            org.opengis.metadata.MetadataExtensionInformation.class,
+//          org.opengis.metadata.Obligation.class, // Excluded CodeList because it doesn't use the usual kind of adapter.
+            org.opengis.metadata.PortrayalCatalogueReference.class,
+            org.opengis.metadata.acquisition.AcquisitionInformation.class,
+            org.opengis.metadata.acquisition.Context.class,
+            org.opengis.metadata.acquisition.EnvironmentalRecord.class,
+            org.opengis.metadata.acquisition.Event.class,
+            org.opengis.metadata.acquisition.GeometryType.class,
+            org.opengis.metadata.acquisition.Instrument.class,
+            org.opengis.metadata.acquisition.Objective.class,
+            org.opengis.metadata.acquisition.ObjectiveType.class,
+            org.opengis.metadata.acquisition.Operation.class,
+            org.opengis.metadata.acquisition.OperationType.class,
+            org.opengis.metadata.acquisition.Plan.class,
+            org.opengis.metadata.acquisition.Platform.class,
+            org.opengis.metadata.acquisition.PlatformPass.class,
+            org.opengis.metadata.acquisition.Priority.class,
+            org.opengis.metadata.acquisition.RequestedDate.class,
+            org.opengis.metadata.acquisition.Requirement.class,
+            org.opengis.metadata.acquisition.Sequence.class,
+            org.opengis.metadata.acquisition.Trigger.class,
+            org.opengis.metadata.citation.Address.class,
+            org.opengis.metadata.citation.Citation.class,
+            org.opengis.metadata.citation.CitationDate.class,
+            org.opengis.metadata.citation.Contact.class,
+            org.opengis.metadata.citation.DateType.class,
+            org.opengis.metadata.citation.OnLineFunction.class,
+            org.opengis.metadata.citation.OnlineResource.class,
+            org.opengis.metadata.citation.PresentationForm.class,
+            org.opengis.metadata.citation.ResponsibleParty.class,
+            org.opengis.metadata.citation.Role.class,
+            org.opengis.metadata.citation.Series.class,
+            org.opengis.metadata.citation.Telephone.class,
+            org.opengis.metadata.constraint.Classification.class,
+            org.opengis.metadata.constraint.Constraints.class,
+            org.opengis.metadata.constraint.LegalConstraints.class,
+            org.opengis.metadata.constraint.Restriction.class,
+            org.opengis.metadata.constraint.SecurityConstraints.class,
+            org.opengis.metadata.content.Band.class,
+            org.opengis.metadata.content.BandDefinition.class,
+            org.opengis.metadata.content.ContentInformation.class,
+            org.opengis.metadata.content.CoverageContentType.class,
+            org.opengis.metadata.content.CoverageDescription.class,
+            org.opengis.metadata.content.FeatureCatalogueDescription.class,
+            org.opengis.metadata.content.ImageDescription.class,
+            org.opengis.metadata.content.ImagingCondition.class,
+            org.opengis.metadata.content.PolarizationOrientation.class,
+            org.opengis.metadata.content.RangeDimension.class,
+            org.opengis.metadata.content.RangeElementDescription.class,
+            org.opengis.metadata.content.TransferFunctionType.class,
+            org.opengis.metadata.distribution.DataFile.class,
+            org.opengis.metadata.distribution.DigitalTransferOptions.class,
+            org.opengis.metadata.distribution.Distribution.class,
+            org.opengis.metadata.distribution.Distributor.class,
+            org.opengis.metadata.distribution.Format.class,
+            org.opengis.metadata.distribution.Medium.class,
+            org.opengis.metadata.distribution.MediumFormat.class,
+            org.opengis.metadata.distribution.MediumName.class,
+            org.opengis.metadata.distribution.StandardOrderProcess.class,
+            org.opengis.metadata.extent.BoundingPolygon.class,
+            org.opengis.metadata.extent.Extent.class,
+            org.opengis.metadata.extent.GeographicBoundingBox.class,
+            org.opengis.metadata.extent.GeographicDescription.class,
+            org.opengis.metadata.extent.GeographicExtent.class,
+            org.opengis.metadata.extent.SpatialTemporalExtent.class,
+            org.opengis.metadata.extent.TemporalExtent.class,
+            org.opengis.metadata.extent.VerticalExtent.class,
+            org.opengis.metadata.identification.AggregateInformation.class,
+            org.opengis.metadata.identification.AssociationType.class,
+            org.opengis.metadata.identification.BrowseGraphic.class,
+            org.opengis.metadata.identification.CharacterSet.class,
+            org.opengis.metadata.identification.DataIdentification.class,
+            org.opengis.metadata.identification.Identification.class,
+            org.opengis.metadata.identification.InitiativeType.class,
+            org.opengis.metadata.identification.Keywords.class,
+            org.opengis.metadata.identification.KeywordType.class,
+            org.opengis.metadata.identification.Progress.class,
+            org.opengis.metadata.identification.RepresentativeFraction.class,
+            org.opengis.metadata.identification.Resolution.class,
+            org.opengis.metadata.identification.ServiceIdentification.class,
+            org.opengis.metadata.identification.TopicCategory.class,
+            org.opengis.metadata.identification.Usage.class,
+            org.opengis.metadata.lineage.Algorithm.class,
+            org.opengis.metadata.lineage.Lineage.class,
+            org.opengis.metadata.lineage.NominalResolution.class,
+            org.opengis.metadata.lineage.Processing.class,
+            org.opengis.metadata.lineage.ProcessStep.class,
+            org.opengis.metadata.lineage.ProcessStepReport.class,
+            org.opengis.metadata.lineage.Source.class,
+            org.opengis.metadata.maintenance.MaintenanceFrequency.class,
+            org.opengis.metadata.maintenance.MaintenanceInformation.class,
+            org.opengis.metadata.maintenance.ScopeCode.class,
+            org.opengis.metadata.maintenance.ScopeDescription.class,
+            org.opengis.metadata.quality.AbsoluteExternalPositionalAccuracy.class,
+            org.opengis.metadata.quality.AccuracyOfATimeMeasurement.class,
+            org.opengis.metadata.quality.Completeness.class,
+            org.opengis.metadata.quality.CompletenessCommission.class,
+            org.opengis.metadata.quality.CompletenessOmission.class,
+            org.opengis.metadata.quality.ConceptualConsistency.class,
+            org.opengis.metadata.quality.ConformanceResult.class,
+            org.opengis.metadata.quality.CoverageResult.class,
+            org.opengis.metadata.quality.DataQuality.class,
+            org.opengis.metadata.quality.DomainConsistency.class,
+            org.opengis.metadata.quality.Element.class,
+            org.opengis.metadata.quality.EvaluationMethodType.class,
+            org.opengis.metadata.quality.FormatConsistency.class,
+            org.opengis.metadata.quality.GriddedDataPositionalAccuracy.class,
+            org.opengis.metadata.quality.LogicalConsistency.class,
+            org.opengis.metadata.quality.NonQuantitativeAttributeAccuracy.class,
+            org.opengis.metadata.quality.PositionalAccuracy.class,
+            org.opengis.metadata.quality.QuantitativeAttributeAccuracy.class,
+            org.opengis.metadata.quality.QuantitativeResult.class,
+            org.opengis.metadata.quality.RelativeInternalPositionalAccuracy.class,
+            org.opengis.metadata.quality.Result.class,
+            org.opengis.metadata.quality.Scope.class,
+            org.opengis.metadata.quality.TemporalAccuracy.class,
+            org.opengis.metadata.quality.TemporalConsistency.class,
+            org.opengis.metadata.quality.TemporalValidity.class,
+            org.opengis.metadata.quality.ThematicAccuracy.class,
+            org.opengis.metadata.quality.ThematicClassificationCorrectness.class,
+            org.opengis.metadata.quality.TopologicalConsistency.class,
+            org.opengis.metadata.quality.Usability.class,
+            org.opengis.metadata.spatial.CellGeometry.class,
+            org.opengis.metadata.spatial.Dimension.class,
+            org.opengis.metadata.spatial.DimensionNameType.class,
+            org.opengis.metadata.spatial.GCP.class,
+            org.opengis.metadata.spatial.GCPCollection.class,
+            org.opengis.metadata.spatial.GeolocationInformation.class,
+            org.opengis.metadata.spatial.GeometricObjects.class,
+            org.opengis.metadata.spatial.GeometricObjectType.class,
+            org.opengis.metadata.spatial.Georectified.class,
+            org.opengis.metadata.spatial.Georeferenceable.class,
+            org.opengis.metadata.spatial.GridSpatialRepresentation.class,
+            org.opengis.metadata.spatial.PixelOrientation.class,
+            org.opengis.metadata.spatial.SpatialRepresentation.class,
+            org.opengis.metadata.spatial.SpatialRepresentationType.class,
+            org.opengis.metadata.spatial.TopologyLevel.class,
+            org.opengis.metadata.spatial.VectorSpatialRepresentation.class);
+    }
+
+    /**
+     * Returns the expected namespace for an element defined by the given specification.
+     * For example the namespace of any type defined by {@link Specification#ISO_19115}
+     * is {@code "http://www.isotc211.org/2005/gmd"}.
+     */
+    @Override
+    protected String getExpectedNamespace(final Class<?> impl, final Specification specification) {
+        if (impl == org.apache.sis.metadata.iso.identification.DefaultServiceIdentification.class) {
+            return Namespaces.SRV;
+        }
+        return super.getExpectedNamespace(impl, specification);
+    }
+
+    /**
+     * Returns the type of the given element, or {@code null} if the type is not yet
+     * determined (the later cases could change in a future version).
+     */
+    @Override
+    protected String getExpectedTypeForElement(final Class<?> type, final Class<?> impl) {
+        final String rootName = type.getAnnotation(UML.class).identifier();
+        switch (rootName) {
+            // We don't know yet what is the type of this one.
+            case "MD_FeatureTypeList": {
+                return null;
+            }
+            // Following prefix was changed in ISO 19115 corrigendum,
+            // but ISO 19139 still use the old prefix.
+            case "SV_ServiceIdentification": {
+                return "MD_ServiceIdentification_Type";
+            }
+        }
+        final StringBuilder buffer = new StringBuilder(rootName.length() + 13);
+        if (impl.getSimpleName().startsWith("Abstract")) {
+            buffer.append("Abstract");
+        }
+        return buffer.append(rootName).append("_Type").toString();
+    }
+
+    /**
+     * Returns the ISO 19139 wrapper for the given GeoAPI type,
+     * or {@code null} if no adapter is expected for the given type.
+     */
+    @Override
+    protected Class<?> getWrapperFor(final Class<?> type) throws ClassNotFoundException {
+        if (type.equals(org.opengis.metadata.Metadata.class)) {
+            /*
+             * We don't have adapter for Metadata, since it is the root element.
+             * We explicitly exclude it for avoiding confusion with PropertyType,
+             * which is the base class of all other adapters.
+             */
+            return null;
+        }
+        final String classname = "org.apache.sis.internal.jaxb." +
+              (CodeList.class.isAssignableFrom(type) ? "code" : "metadata") +
+              '.' + type.getAnnotation(UML.class).identifier();
+        final Class<?> wrapper = Class.forName(classname);
+        assertTrue("Expected a final class for " + wrapper.getName(), Modifier.isFinal(wrapper.getModifiers()));
+        return wrapper;
+    }
+
+    /**
+     * Return {@code false} for the Apache SIS properties which are known to have no setter methods.
+     */
+    @Override
+    protected boolean isWritable(final Class<?> impl, final String property) {
+        if (DefaultRepresentativeFraction.class.isAssignableFrom(impl)) {
+            if (property.equals("doubleValue")) {
+                return false;
+            }
+        }
+        if (AbstractElement.class.isAssignableFrom(impl)) {
+            if (property.equals("dates")) {
+                return false;
+            }
+        }
+        return super.isWritable(impl, property);
+    }
+}

Propchange: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java?rev=1470608&r1=1470607&r2=1470608&view=diff
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -42,7 +42,8 @@ import org.junit.BeforeClass;
     org.apache.sis.metadata.InformationMapTest.class,
     org.apache.sis.metadata.ValueMapTest.class,
     org.apache.sis.metadata.MetadataStandardTest.class,
-    org.apache.sis.metadata.PrunerTest.class
+    org.apache.sis.metadata.PrunerTest.class,
+    org.apache.sis.metadata.iso.AllMetadataTest.class
 })
 public final strictfp class MetadataTestSuite extends TestSuite {
     /**

Modified: sis/branches/JDK7/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java?rev=1470608&r1=1470607&r2=1470608&view=diff
==============================================================================
--- sis/branches/JDK7/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -20,6 +20,7 @@ import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.test.DependsOnMethod;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -451,9 +452,25 @@ public strictfp class GeneralEnvelopeTes
     }
 
     /**
+     * Tests the {@link GeneralEnvelope#toString()} method.
+     */
+    @Test
+    public void testToString() {
+        GeneralEnvelope envelope = new GeneralEnvelope(new double[] {-180, -90}, new double[] {180, 90});
+        assertEquals("BOX(-180 -90, 180 90)", envelope.toString());
+
+        envelope = new GeneralEnvelope(3);
+        envelope.setRange(0, -180, +180);
+        envelope.setRange(1,  -90,  +90);
+        envelope.setRange(2,   10,   30);
+        assertEquals("BOX3D(-180 -90 10, 180 90 30)", envelope.toString());
+    }
+
+    /**
      * Tests the {@link GeneralEnvelope#GeneralEnvelope(CharSequence)} constructor.
      */
     @Test
+    @DependsOnMethod("testToString")
     public void testWktParsing() {
         GeneralEnvelope envelope = new GeneralEnvelope("BOX(-180 -90,180 90)");
         assertEquals(2, envelope.getDimension());

Added: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java?rev=1470608&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java (added)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -0,0 +1,672 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.test;
+
+import java.util.Set;
+import java.util.HashSet;
+import java.lang.reflect.Method;
+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.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import org.opengis.annotation.Obligation;
+import org.opengis.annotation.Specification;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.xml.Namespaces;
+import org.junit.After;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.apache.sis.test.TestUtilities.getSingleton;
+
+
+/**
+ * Base class for validations of {@link UML}, {@link XmlElement} and other annotations.
+ * Some tests performed by this class are:
+ *
+ * <ul>
+ *   <li>All implementation classes have {@link XmlRootElement} and {@link XmlType} annotations.</li>
+ *   <li>The name declared in the {@code XmlType} annotations matches the
+ *       {@linkplain #getExpectedTypeForElement expected value}.</li>
+ *   <li>The name declared in the {@code XmlRootElement} (classes) or {@link XmlElement} (methods)
+ *       annotations matches the identifier declared in the {@link UML} annotation of the GeoAPI interfaces.</li>
+ *   <li>The {@code XmlElement.required()} boolean is consistent with the UML {@linkplain Obligation obligation}.</li>
+ *   <li>The namespace declared in the {@code XmlRootElement} or {@code XmlElement} annotations
+ *       is not redundant with the {@link XmlSchema} annotation in the package.</li>
+ *   <li>The prefixes declared in the {@link XmlNs} annotations match the
+ *       {@linkplain Namespaces#getPreferredPrefix expected prefixes}.</li>
+ *   <li>The {@linkplain #getWrapperFor wrapper}, if any, is consistent.</li>
+ * </ul>
+ *
+ * @author  Cédric Briançon (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.05)
+ * @version 0.3
+ * @module
+ */
+public abstract strictfp class AnnotationsTestCase extends TestCase {
+    /**
+     * The {@value} string used in JAXB annotations for default names or namespaces.
+     */
+    private static final String DEFAULT = "##default";
+
+    /**
+     * The GeoAPI interfaces or {@link CodeList} types to test.
+     */
+    protected final Class<?>[] types;
+
+    /**
+     * The type being tested, or {@code null} if none. In case of test failure, this information
+     * will be used by {@link #printFailureLocation()} for formatting a message giving the name
+     * of class and method where the failure occurred.
+     */
+    protected String testingClass;
+
+    /**
+     * The method being tested, or {@code null} if none. In case of test failure, this information
+     * will be used by {@link #printFailureLocation()} for formatting a message giving the name of
+     * class and method where the failure occurred.
+     */
+    protected String testingMethod;
+
+    /**
+     * Creates a new test suite for the given types.
+     *
+     * @param types The GeoAPI interfaces or {@link CodeList} types to test.
+     */
+    protected AnnotationsTestCase(final Class<?>... types) {
+        this.types = types;
+    }
+
+    /**
+     * Returns the SIS implementation class for the given GeoAPI interface.
+     * For example the implementation of the {@link org.opengis.metadata.citation.Citation}
+     * interface is the {@link org.apache.sis.metadata.iso.citation.DefaultCitation} class.
+     *
+     * @param  <T>  The type represented by the {@code type} argument.
+     * @param  type The GeoAPI interface (never a {@link CodeList} type).
+     * @return The SIS implementation for the given interface.
+     */
+    protected abstract <T> Class<? extends T> getImplementation(Class<T> type);
+
+    /**
+     * If the given GeoAPI type, when marshalled to XML, is wrapped into an other XML element,
+     * returns the class of the wrapper for that XML element. Otherwise returns {@code null}.
+     * Such wrappers are unusual in XML (except for lists), but the ISO 19139 standard do that
+     * systematically for every elements.
+     *
+     * <p><b>Example:</b> when a {@link org.apache.sis.metadata.iso.citation.DefaultContact}
+     * is marshalled to XML inside a {@code ResponsibleParty}, the element is not marshalled
+     * directly inside its parent as we usually do in XML. Instead, we have a {@code <CI_Contact>}.
+     * inside the {@code <contactInfo>} element as below:</p>
+     *
+     * {@preformat xml
+     *   <CI_ResponsibleParty>
+     *     <contactInfo>
+     *       <CI_Contact>
+     *         ...
+     *       </CI_Contact>
+     *     </contactInfo>
+     *   </CI_ResponsibleParty>
+     * }
+     *
+     * To reflect that fact, this method shall return the {@link org.apache.sis.internal.jaxb.metadata.CI_Contact}
+     * wrapper class for the {@link org.apache.sis.metadata.iso.citation.DefaultCitation} argument.
+     * If no wrapper is expected for the given class, then this method shall return {@code null}.
+     *
+     * <p>If a wrapper is expected for the given class but was not found, then this method shall throw
+     * {@link ClassNotFoundException}. Note that no wrapper may be defined explicitely for the given type,
+     * while a wrapper is defined for a parent of the given type. This method does not need to care about
+     * such situation, since the caller will automatically searches for a parent class if
+     * {@code ClassNotFoundException} has been thrown.</p>
+     *
+     * <p>In SIS implementation, most wrappers are also {@link javax.xml.bind.annotation.adapters.XmlAdapter}.
+     * But this is not a requirement.</p>
+     *
+     * @param  type The GeoAPI interface or {@link CodeList} type.
+     * @return The wrapper for the given type, or {@code null} if none.
+     * @throws ClassNotFoundException If a wrapper was expected but not found.
+     */
+    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.
+     */
+    private static final class WrapperClass {
+        final Class<?> type;
+        boolean isInherited;
+
+        WrapperClass(final Class<?> type) {
+            this.type = type;
+        }
+    }
+
+    /**
+     * Returns the value of {@link #getWrapperFor(Class)} for the given class, or for a parent
+     * of the given class if {@code getWrapperFor(Class)} threw {@code ClassNotFoundException}.
+     *
+     * @param  type The GeoAPI interface or {@link CodeList} type.
+     * @return The wrapper for the given type. {@link WrapperClass#type} is {@code null} if
+     *         no wrapper has been found.
+     * @throws ClassNotFoundException If a wrapper was expected but not found in the
+     *         given type neither in any of the parent classes.
+     */
+    private WrapperClass getWrapperInHierarchy(final Class<?> type) throws ClassNotFoundException {
+        try {
+            return new WrapperClass(getWrapperFor(type));
+        } catch (ClassNotFoundException e) {
+            for (final Class<?> parent : type.getInterfaces()) {
+                if (ArraysExt.containsIdentity(types, parent)) try {
+                    final WrapperClass wrapper = getWrapperInHierarchy(parent);
+                    wrapper.isInherited = true;
+                    return wrapper;
+                } catch (ClassNotFoundException e2) {
+                    e.addSuppressed(e2);
+                }
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Returns the XML type for an element of the given type. For example in ISO 19139,
+     * the XML type of {@code CI_Citation} is {@code CI_Citation_Type}.
+     *
+     * @param  type The GeoAPI interface.
+     * @param  impl The implementation class.
+     * @return The name of the XML type for the given element, or {@code null} if none.
+     *
+     * @see #testImplementationAnnotations()
+     */
+    protected abstract String getExpectedTypeForElement(Class<?> type, Class<?> impl);
+
+    /**
+     * Returns the expected namespace for an element defined by the given specification.
+     * For example the namespace of any type defined by {@link Specification#ISO_19115}
+     * is {@code "http://www.isotc211.org/2005/gmd"}.
+     *
+     * <p>The default implementation recognizes the
+     * {@linkplain Specification#ISO_19115 ISO 19115},
+     * {@linkplain Specification#ISO_19115_2 ISO 19115-2},
+     * {@linkplain Specification#ISO_19139 ISO 19139} and
+     * {@linkplain Specification#ISO_19108 ISO 19108} specifications.
+     * Subclasses shall override this method if they need to support more namespaces.</p>
+     *
+     * <p>The prefix for the given namespace will be fetched by
+     * {@link Namespaces#getPreferredPrefix(String, String)}.</p>
+     *
+     * @param  impl The implementation class or {@link CodeList} type.
+     * @param  specification The specification that define the type, or {@code null} if unspecified.
+     * @return The expected namespace.
+     * @throws IllegalArgumentException If the given specification is unknown to this method.
+     */
+    protected String getExpectedNamespace(final Class<?> impl, final Specification specification) {
+        switch (specification) {
+            case ISO_19115:   return Namespaces.GMD;
+            case ISO_19115_2: return Namespaces.GMI;
+            case ISO_19139:   return Namespaces.GMX;
+            case ISO_19108:   return Namespaces.GMD;
+            default: throw new IllegalArgumentException(specification.toString());
+        }
+    }
+
+    /**
+     * Replaces {@value #DEFAULT} value by the {@link XmlSchema} namespace if needed,
+     * then performs validity check on the resulting namespace. This method checks that:
+     *
+     * <ul>
+     *   <li>The namespace is not redundant with the package-level {@link XmlSchema} namespace.</li>
+     *   <li>The namespace is declared in a package-level {@link XmlNs} annotation.</li>
+     *   <li>The namespace is equals to the {@linkplain #getExpectedNamespace expected namespace}.</li>
+     * </ul>
+     *
+     * @param  namespace The namespace given by the {@code @XmlRootElement} or {@code @XmlElement} annotation.
+     * @param  impl      The implementation or wrapper class for which to get the package namespace.
+     * @param  uml       The {@code @UML} annotation, or {@code null} if none.
+     * @return The actual namespace (same as {@code namespace} if it was not {@value #DEFAULT}).
+     */
+    private String assertExpectedNamespace(String namespace, final Class<?> impl, final UML uml) {
+        assertNotNull("Missing namespace.", namespace);
+        assertFalse("Missing namespace.", namespace.trim().isEmpty());
+        /*
+         * Get the namespace declared at the package level, and ensure the the
+         * given namespace is not redundant with that package-level namespace.
+         */
+        final XmlSchema schema = impl.getPackage().getAnnotation(XmlSchema.class);
+        assertNotNull("Missing @XmlSchema package annotation.", schema);
+        final String schemaNamespace = schema.namespace();
+        assertFalse("Missing namespace in @XmlSchema package annotation.", schemaNamespace.trim().isEmpty());
+        assertFalse("Namespace declaration is redundant with @XmlSchema.", namespace.equals(schemaNamespace));
+        /*
+         * Check that the namespace is declared in the package-level @XmlNs annotation.
+         * We do not verify the validity of those @XmlNs annotations, since this is the
+         * purpose of the 'testPackageAnnotations()' method.
+         */
+        if (!DEFAULT.equals(namespace)) {
+            boolean found = false;
+            for (final XmlNs ns : schema.xmlns()) {
+                if (namespace.equals(ns.namespaceURI())) {
+                    found = true;
+                    break;
+                }
+            }
+            assertTrue("Namespace is not declared in the package @XmlSchema.xmlns().", found);
+        } else {
+            namespace = schemaNamespace;
+        }
+        assertEquals("Wrong namespace for the ISO specification.",
+                getExpectedNamespace(impl, (uml != null) ? uml.specification() : null), namespace);
+        return namespace;
+    }
+
+    /**
+     * Returns the namespace declared in the {@link XmlSchema} annotation of the given package,
+     * or {@code null} if none.
+     *
+     * @param p The package, or {@code null}.
+     * @return The namespace, or {@code null} if none.
+     */
+    private static String getNamespace(final Package p) {
+        if (p != null) {
+            final XmlSchema schema = p.getAnnotation(XmlSchema.class);
+            if (schema != null) {
+                final String namespace = schema.namespace().trim();
+                if (!namespace.isEmpty() && !DEFAULT.equals(namespace)) {
+                    return namespace;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the namespace declared in the {@link XmlRootElement} annotation of the given class,
+     * or the package annotation if none is found in the class.
+     *
+     * @param  impl The implementation class, or {@code null}.
+     * @return The namespace, or {@code null} if none.
+     */
+    private static String getNamespace(final Class<?> impl) {
+        if (impl == null) {
+            return null;
+        }
+        final XmlRootElement root = impl.getAnnotation(XmlRootElement.class);
+        if (root != null) {
+            final String namespace = root.namespace().trim();
+            if (!namespace.isEmpty() && !DEFAULT.equals(namespace)) {
+                return namespace;
+            }
+        }
+        return getNamespace(impl.getPackage());
+    }
+
+    /**
+     * Gets the {@link XmlElement} annotation for the no-argument method of the given name
+     * in the given implementation class. If the method is not annotated, then fallback on
+     * a field having the same name than the UML identifier. If no such field is found or
+     * is annotated, returns {@code null}.
+     *
+     * @param  impl   The implementation class.
+     * @param  method The name of the getter method to search for.
+     * @param  uml    The UML annotation on the GeoAPI interface, or {@code null} if none.
+     * @return The {@code XmlElement}, or {@code null} if none.
+     */
+    private static XmlElement getXmlElement(final Class<?> impl, final String method, final UML uml) {
+        XmlElement element = null;
+        try {
+            element = impl.getMethod(method, (Class<?>[]) null).getAnnotation(XmlElement.class);
+            if (element == null && uml != null) {
+                element = impl.getDeclaredField(uml.identifier()).getAnnotation(XmlElement.class);
+            }
+        } catch (NoSuchMethodException ex) {
+            fail("Missing implementation: " + ex);
+        } catch (NoSuchFieldException ex) {
+            // Ignore - we will consider that there is no annotation.
+        }
+        return element;
+    }
+
+    /**
+     * Returns {@code true} if the given method should be ignored.
+     * This method returns {@code true} of deprecated methods and
+     * some standard methods from the JDK.
+     */
+    private static boolean isIgnored(final Method method) {
+        if (method.isAnnotationPresent(Deprecated.class)) {
+            return true;
+        }
+        final String name = method.getName();
+        if (name.equals("equals") || name.equals("hashCode") || name.equals("doubleValue")) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Tests the annotations on every GeoAPI interfaces and code lists in the {@link #types} array.
+     * More specifically this method tests that:
+     *
+     * <ul>
+     *   <li>All elements in {@link #types} except code lists are interfaces.</li>
+     *   <li>All elements in {@code types} have a {@link UML} annotation.</li>
+     *   <li>All methods expect deprecated methods and methods overriding JDK methods
+     *       have a {@link UML} annotation.</li>
+     * </ul>
+     */
+    @Test
+    public void testInterfaceAnnotations() {
+        for (final Class<?> type : types) {
+            testingMethod = null;
+            testingClass = type.getCanonicalName();
+            UML uml = type.getAnnotation(UML.class);
+            assertNotNull("Missing @UML annotation.", uml);
+            if (!CodeList.class.isAssignableFrom(type)) {
+                for (final Method method : type.getDeclaredMethods()) {
+                    testingMethod = method.getName();
+                    if (!isIgnored(method)) {
+                        uml = method.getAnnotation(UML.class);
+                        assertNotNull("Missing @UML annotation.", uml);
+                    }
+                }
+            }
+        }
+        done();
+    }
+
+    /**
+     * Tests the annotations in the {@code package-info} files of SIS implementations of the
+     * interfaces enumerated in the {@code #types} array. More specifically this method tests that:
+     *
+     * <ul>
+     *   <li>The prefixes declared in the {@link XmlNs} annotations match the
+     *       {@linkplain Namespaces#getPreferredPrefix expected prefixes}.</li>
+     * </ul>
+     */
+    @Test
+    public void testPackageAnnotations() {
+        final Set<Package> packages = new HashSet<>();
+        for (final Class<?> type : types) {
+            if (!CodeList.class.isAssignableFrom(type)) {
+                testingClass = type.getCanonicalName();
+                final Class<?> impl = getImplementation(type);
+                if (impl != null) {
+                    testingClass = impl.getCanonicalName();
+                    final Package p = impl.getPackage();
+                    assertNotNull("Missing package information.", p);
+                    packages.add(p);
+                }
+            }
+        }
+        for (final Package p : packages) {
+            for (final XmlNs ns : p.getAnnotation(XmlSchema.class).xmlns()) {
+                testingClass = p.getName();
+                final String namespace = ns.namespaceURI();
+                assertEquals("Unexpected namespace prefix.", Namespaces.getPreferredPrefix(namespace, null), ns.prefix());
+            }
+        }
+        done();
+    }
+
+    /**
+     * Tests the annotations on every SIS implementations of the interfaces enumerated
+     * in the {@link #types} array. More specifically this method tests that:
+     *
+     * <ul>
+     *   <li>All implementation classes have {@link XmlRootElement} and {@link XmlType} annotations.</li>
+     *   <li>The name declared in the {@code XmlType} annotations matches the
+     *       {@linkplain #getExpectedTypeForElement expected value}.</li>
+     *   <li>The name declared in the {@code XmlRootElement} annotations matches the identifier declared
+     *       in the {@link UML} annotation of the GeoAPI interfaces.</li>
+     *   <li>The namespace declared in the {@code XmlRootElement} annotations is not redundant with
+     *       the {@link XmlSchema} annotation in the package.</li>
+     * </ul>
+     *
+     * This method does not check the method annotations, since it is {@link #testMethodAnnotations()} job.
+     */
+    @Test
+    @DependsOnMethod("testInterfaceAnnotations")
+    public void testImplementationAnnotations() {
+        for (final Class<?> type : types) {
+            if (CodeList.class.isAssignableFrom(type)) {
+                // Skip code lists, since they are not the purpose of this test.
+                continue;
+            }
+            testingClass = type.getCanonicalName();
+            /*
+             * Get the implementation class, which is mandatory (otherwise the
+             * subclass shall not include the interface in the 'types' array).
+             */
+            final Class<?> impl = getImplementation(type);
+            assertNotNull("No implementation found.", impl);
+            assertNotSame("No implementation found.", type, impl);
+            testingClass = impl.getCanonicalName();
+            /*
+             * Compare the XmlRootElement with the UML annotation, if any. The UML annotation
+             * is mandatory in the default implementation of the 'testInterfaceAnnotations()'
+             * method, but we don't require the UML to be non-null here since this is not the
+             * job of this test method. This is because subclasses may choose to override the
+             * 'testInterfaceAnnotations()' method.
+             */
+            final XmlRootElement root = impl.getAnnotation(XmlRootElement.class);
+            assertNotNull("Missing @XmlRootElement annotation.", root);
+            final UML uml = type.getAnnotation(UML.class);
+            if (uml != null) {
+                assertEquals("Wrong @XmlRootElement.name().", uml.identifier(), root.name());
+            }
+            /*
+             * Check that the namespace is the expected one (according subclass)
+             * and is not redundant with the package @XmlSchema annotation.
+             */
+            assertExpectedNamespace(root.namespace(), impl, uml);
+            /*
+             * Compare the XmlType annotation with the expected value.
+             */
+            final XmlType xmlType = impl.getAnnotation(XmlType.class);
+            assertNotNull("Missing @XmlType annotation.", xmlType);
+            String expected = getExpectedTypeForElement(type, impl);
+            if (expected == null) {
+                expected = DEFAULT;
+            }
+            assertEquals("Wrong @XmlType.name().", expected, xmlType.name());
+        }
+        done();
+    }
+
+    /**
+     * Tests the annotations on every methods of SIS classes.
+     * More specifically this method tests that:
+     *
+     * <ul>
+     *   <li>The name declared in {@link XmlElement} matches the UML identifier.</li>
+     *   <li>The {@code XmlElement.required()} boolean is consistent with the UML {@linkplain Obligation obligation}.</li>
+     *   <li>The namespace declared in {@code XmlElement} is not redundant with the one declared in the package.</li>
+     * </ul>
+     */
+    @Test
+    @DependsOnMethod("testImplementationAnnotations")
+    public void testMethodAnnotations() {
+        for (final Class<?> type : types) {
+            if (CodeList.class.isAssignableFrom(type)) {
+                // Skip code lists, since they are not the purpose of this test.
+                continue;
+            }
+            testingMethod = null;
+            testingClass = type.getCanonicalName();
+            final Class<?> impl = getImplementation(type);
+            if (impl == null) {
+                // Implementation existence are tested by 'testImplementationAnnotations()'.
+                // It is not the purpose of this test to verify again their existence.
+                continue;
+            }
+            testingClass = impl.getCanonicalName();
+            for (final Method method : type.getDeclaredMethods()) {
+                if (isIgnored(method)) {
+                    continue;
+                }
+                testingMethod = method.getName();
+                final UML uml = method.getAnnotation(UML.class);
+                final XmlElement element = getXmlElement(impl, testingMethod, uml);
+                /*
+                 * Just display the missing @XmlElement annotation for the method, since we know
+                 * that some elements are not yet implemented (and consequently can not yet be
+                 * annotated).
+                 */
+                if (element == null) {
+                    // Note: lines with the "[WARNING]" string are highlighted by Jenkins.
+                    warning("[WARNING] Missing @XmlElement annotation for ");
+                    continue;
+                }
+                /*
+                 * The UML annotation is mandatory in the default implementation of the
+                 * 'testInterfaceAnnotations()' method, but we don't require the UML to
+                 * be non-null here since this is not the job of this test method. This
+                 * is because subclasses may choose to override the above test method.
+                 */
+                if (uml != null) {
+                    assertEquals("Wrong @XmlElement.name().", uml.identifier(), element.name());
+                    assertEquals("Wrong @XmlElement.required().", uml.obligation() == Obligation.MANDATORY, element.required());
+                }
+                /*
+                 * Check that the namespace is the expected one (according subclass)
+                 * and is not redundant with the package @XmlSchema annotation.
+                 */
+                assertExpectedNamespace(element.namespace(), impl, uml);
+            }
+        }
+        done();
+    }
+
+    /**
+     * Tests the annotations on wrappers returned by {@link #getWrapperFor(Class)}.
+     * More specifically this method tests that:
+     *
+     * <ul>
+     *   <li>The wrapper have a getter and a setter method declared in the same class.</li>
+     *   <li>The getter method is annotated with {@code @XmlElement} or {@code @XmlElementRef}, but not both</li>
+     *   <li>{@code @XmlElementRef} is used only in parent classes, not in leaf classes.</li>
+     *   <li>The name declared in {@code @XmlElement} matches the {@code @UML} identifier.</li>
+     * </ul>
+     */
+    @Test
+    public void testWrapperAnnotations() {
+        for (final Class<?> type : types) {
+            testingClass = type.getCanonicalName();
+            /*
+             * Check the annotation on the wrapper, if there is one. If no wrapper is declared
+             * specifically for the current type, check if a wrapper is defined for the parent
+             * interface. In such case, the getElement() method is required to be annotated by
+             * @XmlElementRef, not @XmlElement, in order to let JAXB infer the name from the
+             * actual subclass.
+             */
+            final WrapperClass wrapper;
+            try {
+                wrapper = getWrapperInHierarchy(type);
+            } catch (ClassNotFoundException e) {
+                fail(e.toString());
+                continue;
+            }
+            if (wrapper.type == null) {
+                // If the wrapper is intentionally undefined, skip it.
+                continue;
+            }
+            /*
+             * Now fetch the getter/setter methods, ensure that they are declared in the same class
+             * and verify that exactly one of @XmlElement or @XmlElementRef annotation is declared.
+             */
+            testingClass = wrapper.type.getCanonicalName();
+            final Method getter, setter;
+            try {
+                getter = wrapper.type.getMethod("getElement", (Class<?>[]) null);
+                setter = wrapper.type.getMethod("setElement", getter.getReturnType());
+            } catch (NoSuchMethodException e) {
+                fail(e.toString());
+                continue;
+            }
+            assertEquals("The setter method must be declared in the same class than the " +
+                         "getter method - not in a parent class, to avoid issues with JAXB.",
+                         getter.getDeclaringClass(), setter.getDeclaringClass());
+            assertEquals("The setter parameter type shall be the same than the getter return type.",
+                         getter.getReturnType(), getSingleton(setter.getParameterTypes()));
+            final XmlElement element = getter.getAnnotation(XmlElement.class);
+            assertEquals("Expected @XmlElement XOR @XmlElementRef.", (element == null),
+                         getter.isAnnotationPresent(XmlElementRef.class));
+            /*
+             * If the annotation is @XmlElement, ensure that XmlElement.name() is equals to
+             * the UML identifier. Then verify that the
+             */
+            if (element != null) {
+                assertFalse("Expected @XmlElementRef.", wrapper.isInherited);
+                final UML uml = type.getAnnotation(UML.class);
+                if (uml != null) { // 'assertNotNull' is 'testInterfaceAnnotations()' job.
+                    assertEquals("Wrong @XmlElement.", uml.identifier(), element.name());
+                }
+                final String namespace = assertExpectedNamespace(element.namespace(), wrapper.type, uml);
+                if (!CodeList.class.isAssignableFrom(type)) {
+                    final String expected = getNamespace(getImplementation(type));
+                    if (expected != null) { // 'assertNotNull' is 'testImplementationAnnotations()' job.
+                        assertEquals("Inconsistent @XmlRootElement namespace.", expected, namespace);
+                    }
+                }
+            }
+        }
+        done();
+    }
+
+    /**
+     * Shall be invoked after every successful test in order
+     * to disable the report of failed class or method.
+     */
+    private void done() {
+        testingClass  = null;
+        testingMethod = null;
+    }
+
+    /**
+     * Prints the given message followed by the name of the class being tested.
+     */
+    private void warning(String message) {
+        if (testingClass != null) {
+            final StringBuilder buffer = new StringBuilder(message);
+            buffer.append(testingClass);
+            if (testingMethod != null) {
+                buffer.append('.').append(testingMethod).append("()");
+            }
+            message = buffer.toString();
+        }
+        out.println(message);
+    }
+
+    /**
+     * If a test failed, reports the class and method names were the failure occurred.
+     * The message will be written in the {@link #out} printer.
+     *
+     * @see #testingClass
+     * @see #testingMethod
+     */
+    @After
+    public final void printFailureLocation() {
+        if (testingClass != null) {
+            warning("TEST FAILURE: ");
+        }
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java?rev=1470608&r1=1470607&r2=1470608&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java [UTF-8] Mon Apr 22 16:32:37 2013
@@ -200,6 +200,20 @@ public final strictfp class TestUtilitie
     }
 
     /**
+     * Returns the single element from the given array. If the given array is null or
+     * does not contains exactly one element, then an {@link AssertionError} is thrown.
+     *
+     * @param  <E> The type of array elements.
+     * @param  array The array from which to get the singleton.
+     * @return The singleton element from the array.
+     */
+    public static <E> E getSingleton(final E[] array) {
+        assertNotNull("Null array.", array);
+        assertEquals("Not a singleton array.", 1, array.length);
+        return array[0];
+    }
+
+    /**
      * Returns the single element from the given collection. If the given collection is null
      * or does not contains exactly one element, then an {@link AssertionError} is thrown.
      *



Mime
View raw message