sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1634856 - in /sis/trunk: ./ core/sis-metadata/src/main/java/org/apache/sis/metadata/ core/sis-metadata/src/test/java/org/apache/sis/metadata/ core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/
Date Tue, 28 Oct 2014 12:26:47 GMT
Author: desruisseaux
Date: Tue Oct 28 12:26:46 2014
New Revision: 1634856

URL: http://svn.apache.org/r1634856
Log:
Merge from trunk, but with only partially enabled check for implementation API.
Enabling fully the check for implementation API will require to update some test cases.

Modified:
    sis/trunk/   (props changed)
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/APIVerifier.java

Propchange: sis/trunk/
------------------------------------------------------------------------------
  Merged /sis/branches/JDK8:r1634606-1634824
  Merged /sis/branches/JDK7:r1634608-1634829
  Merged /sis/branches/JDK6:r1634609-1634835

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java?rev=1634856&r1=1634855&r2=1634856&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
[UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
[UTF-8] Tue Oct 28 12:26:46 2014
@@ -107,6 +107,18 @@ public class MetadataStandard implements
     private static final long serialVersionUID = 7549790450195184843L;
 
     /**
+     * {@code true} if implementations can alter the API defined in the interfaces by
+     * adding or removing properties. If {@code true}, then {@link PropertyAccessor}
+     * will check for {@link Deprecated} and {@link org.opengis.annotation.UML}
+     * annotations in the implementation classes in addition to the interfaces.
+     *
+     * <p>A value of {@code true} is useful when Apache SIS implements a newer standard
+     * than GeoAPI, but have a slight performance cost at construction time. Performance
+     * after construction should be the same.</p>
+     */
+    static final boolean IMPLEMENTATION_CAN_ALTER_API = false;
+
+    /**
      * Metadata instances defined in this class. The current implementation does not yet
      * contains the user-defined instances. However this may be something we will need to
      * do in the future.
@@ -181,7 +193,7 @@ public class MetadataStandard implements
     private final MetadataStandard[] dependencies;
 
     /**
-     * Accessors for the specified implementations.
+     * Accessors for the specified implementation classes.
      * The only legal value types are:
      *
      * <ul>
@@ -317,7 +329,7 @@ public class MetadataStandard implements
      */
     final PropertyAccessor getAccessor(final Class<?> implementation, final boolean
mandatory) {
         /*
-         * Check for accessors created by previous call to this method.
+         * Check for accessors created by previous calls to this method.
          * Values are added to this cache but never cleared.
          */
         final Object value = accessors.get(implementation);
@@ -369,14 +381,7 @@ public class MetadataStandard implements
                 if (SpecialCases.isSpecialCase(type)) {
                     accessor = new SpecialCases(citation, type, implementation);
                 } else {
-                    /*
-                     * If "multi-value returns" was allowed in the Java language, the 'onlyUML'
boolean would
-                     * be returned by 'findInterface(Class)' method when it falls in the
special case for the
-                     * UML annotation on implementation class. But since we do not have multi-values,
we have
-                     * to infer it from our knownledge of how 'findInterface(Class)' is implemented.
-                     */
-                    final boolean onlyUML = (type == implementation && !type.isInterface());
-                    accessor = new PropertyAccessor(citation, type, implementation, onlyUML);
+                    accessor = new PropertyAccessor(citation, type, implementation);
                 }
                 return accessor;
             }

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java?rev=1634856&r1=1634855&r2=1634856&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
[UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
[UTF-8] Tue Oct 28 12:26:46 2014
@@ -21,7 +21,6 @@ import java.util.List;
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.HashMap;
-import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Collection;
 import java.lang.reflect.Method;
@@ -87,19 +86,6 @@ import static org.apache.sis.util.collec
  */
 class PropertyAccessor {
     /**
-     * Getters shared between many instances of this class. Two different implementations
-     * may share the same getters but different setters.
-     *
-     * <div class="note"><b>Implementation note:</b>
-     * In the particular case of {@code Class} keys, {@code IdentityHashMap} and {@code HashMap}
have identical
-     * behavior since {@code Class} is final and does not override the {@code equals(Object)}
and {@code hashCode()}
-     * methods. The {@code IdentityHashMap} Javadoc claims that it is faster than the regular
{@code HashMap}.
-     * But maybe the most interesting property is that it allocates less objects since {@code
IdentityHashMap}
-     * implementation doesn't need the chain of objects created by {@code HashMap}.</div>
-     */
-    private static final Map<Class<?>, Method[]> SHARED_GETTERS = new IdentityHashMap<Class<?>,
Method[]>();
-
-    /**
      * Enumeration constants for the {@code mode} argument in the
      * {@link #count(Object, ValueExistencePolicy, int)} method.
      */
@@ -253,14 +239,13 @@ class PropertyAccessor {
      * @param  standard       The standard which define the {@code type} interface.
      * @param  type           The interface implemented by the metadata class.
      * @param  implementation The class of metadata implementations, or {@code type} if none.
-     * @param  onlyUML        {@code true} for taking only the getter methods having a {@link
UML} annotation.
      */
-    PropertyAccessor(final Citation standard, final Class<?> type, final Class<?>
implementation, final boolean onlyUML) {
+    PropertyAccessor(final Citation standard, final Class<?> type, final Class<?>
implementation) {
         assert type.isAssignableFrom(implementation) : implementation;
         this.standard       = standard;
         this.type           = type;
         this.implementation = implementation;
-        this.getters        = getGetters(type, implementation, onlyUML);
+        this.getters        = getGetters(type, implementation);
         int allCount = getters.length;
         int standardCount = allCount;
         if (allCount != 0 && getters[allCount-1] == EXTRA_GETTER) {
@@ -415,70 +400,82 @@ class PropertyAccessor {
      * since it may be shared among many instances of {@code PropertyAccessor}.
      *
      * @param  type The metadata interface.
-     * @param  implementation The class of metadata implementations.
-     * @param  onlyUML {@code true} for taking only the getter methods having a {@link UML}
annotation.
+     * @param  implementation The class of metadata implementations, or {@code type} if none.
      * @return The getters declared in the given interface (never {@code null}).
      */
-    private static Method[] getGetters(final Class<?> type, final Class<?> implementation,
final boolean onlyUML) {
-        synchronized (SHARED_GETTERS) {
-            Method[] getters = SHARED_GETTERS.get(type);
-            if (getters == null) {
-                getters = type.getMethods();
-                // Following is similar in purpose to the PropertyAccessor.mapping field,
-                // but index values are different because of the call to Arrays.sort(...).
-                final Map<String,Integer> mapping = new HashMap<String,Integer>(hashMapCapacity(getters.length));
-                boolean hasExtraGetter = false;
-                int count = 0;
-                for (final Method candidate : getters) {
-                    if (Classes.isPossibleGetter(candidate) && (!onlyUML || candidate.isAnnotationPresent(UML.class)))
{
-                        final String name = candidate.getName();
-                        if (name.startsWith(SET)) { // Paranoiac check.
-                            continue;
-                        }
-                        /*
-                         * At this point, we are ready to accept the method. Before doing
so,
-                         * check if the method override an other method defined in a parent
-                         * class with a covariant return type. The JVM considers such cases
-                         * as two different methods, while from a Java developer point of
-                         * view this is the same method (GEOTK-205).
-                         */
-                        final Integer pi = mapping.put(name, count);
-                        if (pi != null) {
-                            final Class<?> pt = getters[pi].getReturnType();
-                            final Class<?> ct = candidate  .getReturnType();
-                            if (ct.isAssignableFrom(pt)) {
-                                continue; // Previous type was more accurate.
-                            }
-                            if (pt.isAssignableFrom(ct)) {
-                                getters[pi] = candidate;
-                                continue;
-                            }
-                            throw new ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_3,
-                                    Classes.getShortName(type) + '.' + name, ct, pt));
+    private static Method[] getGetters(final Class<?> type, final Class<?> implementation)
{
+        /*
+         * Indices map is used for choosing what to do in case of name collision.
+         */
+        Method[] getters = (MetadataStandard.IMPLEMENTATION_CAN_ALTER_API ? implementation
: type).getMethods();
+        final Map<String,Integer> indices = new HashMap<String,Integer>(hashMapCapacity(getters.length));
+        boolean hasExtraGetter = false;
+        int count = 0;
+        for (Method candidate : getters) {
+            if (Classes.isPossibleGetter(candidate)) {
+                final String name = candidate.getName();
+                if (name.startsWith(SET)) { // Paranoiac check.
+                    continue;
+                }
+                /*
+                 * The candidate method should be declared in the interface. If not, then
we require it to have
+                 * a @UML annotation. The later case happen when the Apache SIS implementation
contains methods
+                 * for a new international standard not yet reflected in the GeoAPI interfaces.
+                 */
+                if (true /*MetadataStandard.IMPLEMENTATION_CAN_ALTER_API*/) {
+                    if (type == implementation) {
+                        if (!type.isInterface() && !candidate.isAnnotationPresent(UML.class))
{
+                            continue; // @UML considered optional only for interfaces.
                         }
-                        getters[count++] = candidate;
-                        if (!hasExtraGetter) {
-                            hasExtraGetter = name.equals(EXTRA_GETTER.getName());
+                    } else if (false) try { // Temporarily disabled.
+                        candidate = type.getMethod(name, (Class[]) null);
+                    } catch (NoSuchMethodException e) {
+                        if (!candidate.isAnnotationPresent(UML.class)) {
+                            continue; // Not a method from an interface, and no @UML in implementation.
                         }
                     }
                 }
                 /*
-                 * Sort the standard methods before to add the extra methods (if any) in
order to
-                 * keep the extra methods last. The code checking for the extra methods require
-                 * them to be last.
+                 * At this point, we are ready to accept the method. Before doing so,
+                 * check if the method override an other method defined in a parent
+                 * class with a covariant return type. The JVM considers such cases
+                 * as two different methods, while from a Java developer point of
+                 * view this is the same method (GEOTK-205).
                  */
-                Arrays.sort(getters, 0, count, new PropertyComparator(implementation));
-                if (!hasExtraGetter) {
-                    if (getters.length == count) {
-                        getters = Arrays.copyOf(getters, count+1);
+                final Integer pi = indices.put(name, count);
+                if (pi != null) {
+                    final Class<?> pt = getters[pi].getReturnType();
+                    final Class<?> ct = candidate  .getReturnType();
+                    if (ct.isAssignableFrom(pt)) {
+                        continue; // Previous type was more accurate.
+                    }
+                    if (pt.isAssignableFrom(ct)) {
+                        getters[pi] = candidate;
+                        continue;
                     }
-                    getters[count++] = EXTRA_GETTER;
+                    throw new ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_3,
+                            Classes.getShortName(type) + '.' + name, ct, pt));
                 }
-                getters = ArraysExt.resize(getters, count);
-                SHARED_GETTERS.put(type, getters);
+                getters[count++] = candidate;
+                if (!hasExtraGetter) {
+                    hasExtraGetter = name.equals(EXTRA_GETTER.getName());
+                }
+            }
+        }
+        /*
+         * Sort the standard methods before to add the extra methods (if any) in order to
+         * keep the extra methods last. The code checking for the extra methods require
+         * them to be last.
+         */
+        Arrays.sort(getters, 0, count, new PropertyComparator(implementation));
+        if (!hasExtraGetter) {
+            if (getters.length == count) {
+                getters = Arrays.copyOf(getters, count+1);
             }
-            return getters;
+            getters[count++] = EXTRA_GETTER;
         }
+        getters = ArraysExt.resize(getters, count);
+        return getters;
     }
 
     /**
@@ -614,10 +611,13 @@ class PropertyAccessor {
     }
 
     /**
-     * Returns {@code true} if the property at the given index is deprecated.
+     * Returns {@code true} if the property at the given index is deprecated, either in the
interface that
+     * declare the method or in the implementation class. A method may be deprecated in the
implementation
+     * but not in the interface when the implementation has been updated for a new standard
+     * while the interface is still reflecting the old standard.
      */
     private boolean isDeprecated(final int index) {
-        return getters[index].isAnnotationPresent(Deprecated.class);
+        return PropertyComparator.isDeprecated(implementation, getters[index]);
     }
 
     /**

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java?rev=1634856&r1=1634855&r2=1634856&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
[UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
[UTF-8] Tue Oct 28 12:26:46 2014
@@ -84,13 +84,19 @@ final class PropertyComparator implement
     private final Map<Object,Integer> order;
 
     /**
+     * The implementation class, or the interface is the implementation class is unknown.
+     */
+    private final Class<?> implementation;
+
+    /**
      * Creates a new comparator for the given implementation class.
      *
-     * @param implementation The implementation class, or {@code null} if unknown.
+     * @param implementation The implementation class, or the interface if the implementation
class is unknown.
      */
     PropertyComparator(Class<?> implementation) {
+        this.implementation = implementation;
         order = new HashMap<Object,Integer>();
-        while (implementation != null) {
+        do {
             final XmlType xml = implementation.getAnnotation(XmlType.class);
             if (xml != null) {
                 final String[] propOrder = xml.propOrder();
@@ -108,7 +114,37 @@ final class PropertyComparator implement
                 }
             }
             implementation = implementation.getSuperclass();
+        } while (implementation != null);
+    }
+
+    /**
+     * Returns {@code true} if the given method is deprecated, either in the interface that
declare the method
+     * or in the implementation class. A method may be deprecated in the implementation but
not in the interface
+     * when the implementation has been updated for a new standard, while the interface is
still reflecting the
+     * old standard.
+     *
+     * @param  implementation The implementation class, or the interface is the implementation
class is unknown.
+     * @param  method The method to check for deprecation.
+     * @return {@code true} if the method is deprecated.
+     */
+    static boolean isDeprecated(final Class<?> implementation, Method method) {
+        if (!MetadataStandard.IMPLEMENTATION_CAN_ALTER_API) {
+            return method.isAnnotationPresent(Deprecated.class);
+        }
+        if (method.isAnnotationPresent(Deprecated.class)) {
+            return true;
+        }
+        if (method.getDeclaringClass() == implementation) {
+            return false;
+        }
+        try {
+            method = implementation.getMethod(method.getName(), (Class[]) null);
+        } catch (NoSuchMethodException e) {
+            // Should never happen since the implementation is supposed to implement
+            // the interface that declare the method given in argument.
+            throw new AssertionError(e);
         }
+        return method.isAnnotationPresent(Deprecated.class);
     }
 
     /**
@@ -116,8 +152,8 @@ final class PropertyComparator implement
      */
     @Override
     public int compare(final Method m1, final Method m2) {
-        final boolean deprecated = m1.isAnnotationPresent(Deprecated.class);
-        if (deprecated != m2.isAnnotationPresent(Deprecated.class)) {
+        final boolean deprecated = isDeprecated(implementation, m1);
+        if (deprecated != isDeprecated(implementation, m2)) {
             return deprecated ? +1 : -1;
         }
         int c = indexOf(m2) - indexOf(m1); // indexOf(…) are sorted in descending order.

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java?rev=1634856&r1=1634855&r2=1634856&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java [UTF-8]
(original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java [UTF-8]
Tue Oct 28 12:26:46 2014
@@ -49,7 +49,7 @@ final class SpecialCases extends Propert
      * @param  implementation The class of metadata implementations, or {@code type} if none.
      */
     SpecialCases(final Citation standard, final Class<?> type, final Class<?>
implementation) {
-        super(standard, type, implementation, false);
+        super(standard, type, implementation);
         assert isSpecialCase(type) : type;
         westBoundLongitude = indexOf("westBoundLongitude", true);
         eastBoundLongitude = indexOf("eastBoundLongitude", true);

Modified: sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java?rev=1634856&r1=1634855&r2=1634856&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
[UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
[UTF-8] Tue Oct 28 12:26:46 2014
@@ -191,7 +191,7 @@ public abstract strictfp class MetadataT
                 final Class<?> impl = getImplementation(type);
                 if (impl != null) {
                     assertTrue(type.isAssignableFrom(impl));
-                    testPropertyValues(new PropertyAccessor(standard.getCitation(), type,
impl, false));
+                    testPropertyValues(new PropertyAccessor(standard.getCitation(), type,
impl));
                 }
             }
         }

Modified: sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java?rev=1634856&r1=1634855&r2=1634856&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
[UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
[UTF-8] Tue Oct 28 12:26:46 2014
@@ -89,7 +89,7 @@ public final strictfp class PropertyAcce
      * Creates a new property accessor for the {@link DefaultCitation} class.
      */
     private static PropertyAccessor createPropertyAccessor() {
-        return new PropertyAccessor(HardCodedCitations.ISO_19115, Citation.class, DefaultCitation.class,
false);
+        return new PropertyAccessor(HardCodedCitations.ISO_19115, Citation.class, DefaultCitation.class);
     }
 
     /**
@@ -195,7 +195,7 @@ public final strictfp class PropertyAcce
     @Test
     @DependsOnMethod("testConstructor")
     public void testConstructorWithInheritance() {
-        assertMappingEquals(new PropertyAccessor(HardCodedCitations.ISO_19115, DataIdentification.class,
DefaultDataIdentification.class, false),
+        assertMappingEquals(new PropertyAccessor(HardCodedCitations.ISO_19115, DataIdentification.class,
DefaultDataIdentification.class),
         //……Declaring type………………………Method………………………………………………………………………JavaBeans………………………………………………………UML
identifier………………………………………Sentence……………………………………………………………Type………………………………………………………………
             Identification.class, "getCitation",                   "citation",          
        "citation",                  "Citation",                     Citation.class,
             Identification.class, "getAbstract",                   "abstract",          
        "abstract",                  "Abstract",                     InternationalString.class,
@@ -231,7 +231,7 @@ public final strictfp class PropertyAcce
     @DependsOnMethod("testConstructorWithInheritance")
     public void testConstructorWithCovariantReturnType() {
         final Class<?> type = GeographicCRS.class;
-        assertMappingEquals(new PropertyAccessor(HardCodedCitations.ISO, type, type, false),
+        assertMappingEquals(new PropertyAccessor(HardCodedCitations.ISO, type, type),
         //……Declaring type……………………………Method……………………………………………JavaBeans……………………………UML
identifier………………Sentence…………………………………Type…………………………………………………………
             GeographicCRS.class,    "getCoordinateSystem", "coordinateSystem", "coordinateSystem",
"Coordinate system",  EllipsoidalCS.class,       // Covariant return type
             GeodeticCRS.class,      "getDatum",            "datum",            "datum", 
          "Datum",              GeodeticDatum.class,       // Covariant return type
@@ -362,7 +362,7 @@ public final strictfp class PropertyAcce
     @org.junit.Ignore("Pending completion of ISO 19115:2014 upgrade.")
     public void testSetDeprecated() {
         final PropertyAccessor accessor = new PropertyAccessor(HardCodedCitations.ISO_19115,
-                CoverageDescription.class, DefaultCoverageDescription.class, false);
+                CoverageDescription.class, DefaultCoverageDescription.class);
         final int indexOfDeprecated  = accessor.indexOf("contentType", true);
         final int indexOfReplacement = accessor.indexOf("attributeGroup", true);
         assertTrue("Deprecated elements shall be sorted after non-deprecated ones.",

Modified: sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/APIVerifier.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/APIVerifier.java?rev=1634856&r1=1634855&r2=1634856&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/APIVerifier.java
[UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/APIVerifier.java
[UTF-8] Tue Oct 28 12:26:46 2014
@@ -16,13 +16,17 @@
  */
 package org.apache.sis.metadata.iso;
 
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import org.opengis.annotation.UML;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.test.TestCase;
@@ -32,11 +36,14 @@ import org.junit.Test;
 
 import static org.junit.Assert.*;
 
+// Branch-dependent imports
+import org.apache.sis.internal.jdk7.JDK7;
+
 
 /**
  * Verifies the API changes caused by the ISO 19115:2003 to ISO 19115:2014 upgrade.
- * This class compares the presence of {@link Deprecated} and {@link UML} annotations
- * against a the content of an automatically generated {@code api-changes.properties} file.
+ * This class compares the presence of {@link Deprecated} and {@link UML} annotations against
the content of an
+ * {@linkplain #listAPIChanges(File, File, Appendable) automatically generated} {@code api-changes.properties}
file.
  * The intend is to ensure that we did not forgot an annotation or put the wrong one.
  *
  * <p>The content of the {@code api-changes.properties} files is typically empty on
Apache SIS
@@ -133,4 +140,105 @@ public final strictfp class APIVerifier 
             assertTrue(classChanges.isEmpty());
         }
     }
+
+    /**
+     * Generates the content of the {@code api-changes.properties} file, except for the comments.
+     * This method can be invoked by the {@code sis-metadata} module maintainer when the
Apache SIS
+     * API diverges from the GeoAPI interfaces.
+     *
+     * <p>This method also opportunistically lists method signature changes if some
are found.
+     * This is is for information purpose and shall not be included in the {@code api-changes.properties}
file.</p>
+     *
+     * @param  releasedJAR Path to the JAR file of the GeoAPI interfaces implemented by the
stable version of Apache SIS.
+     * @param  snapshotJAR Path to the JAR file of the GeoAPI interfaces that we would implement
if it was released.
+     * @param  out Where to write the API differences between {@code releasedJAR} and {@code
snapshotJAR}.
+     * @throws ReflectiveOperationException if an error occurred while processing the JAR
file content.
+     * @throws IOException if an error occurred while reading the JAR files or writing to
{@code out}.
+     */
+    public static void listAPIChanges(final File releasedJAR, final File snapshotJAR, final
Appendable out)
+            throws Exception
+    {
+        final String lineSeparator = JDK7.lineSeparator();
+        final Map<String,Boolean> methodChanges = new TreeMap<String,Boolean>();
+        final List<String> incompatibleChanges = new ArrayList<String>();
+        final ClassLoader parent = APIVerifier.class.getClassLoader();
+        final JarFile newJARContent = new JarFile(snapshotJAR);
+        final URLClassLoader oldAPI = new URLClassLoader(new URL[] {releasedJAR.toURI().toURL()},
parent);
+        final URLClassLoader newAPI = new URLClassLoader(new URL[] {snapshotJAR.toURI().toURL()},
parent);
+        try {
+            final Class<? extends Annotation> newUML = Class.forName("org.opengis.annotation.UML",
false, newAPI).asSubclass(Annotation.class);
+            final Method newIdentifier = newUML.getMethod("identifier", (Class[]) null);
+            final Enumeration<JarEntry> entries = newJARContent.entries();
+            while (entries.hasMoreElements()) {
+                String className = entries.nextElement().getName();
+                if (className.endsWith(".class")) {
+                    className = className.substring(0, className.length() - 6).replace('/',
'.');
+                    final Class<?> newClass = Class.forName(className, false, newAPI);
+                    if (!newClass.isInterface() || !Modifier.isPublic(newClass.getModifiers()))
{
+                        continue;
+                    }
+                    final Class<?> oldClass;
+                    try {
+                        oldClass = Class.forName(className, false, oldAPI);
+                    } catch (ClassNotFoundException e) {
+                        // New class that did not existed in previous release. Ignore.
+                        continue;
+                    }
+                    methodChanges.clear();
+                    for (final Method newMethod : newClass.getDeclaredMethods()) {
+                        if (!Modifier.isPublic(newMethod.getModifiers())) {
+                            continue;
+                        }
+                        final String methodName = newMethod.getName();
+                        final Class<?>[] parameterTypes = newMethod.getParameterTypes();
+                        Method oldMethod;
+                        try {
+                            oldMethod = oldClass.getDeclaredMethod(methodName, parameterTypes);
+                        } catch (NoSuchMethodException e) {
+                            oldMethod = null;
+                        }
+                        if (oldMethod != null) {
+                            final String oldType = oldMethod.getGenericReturnType().toString();
+                            final String newType = newMethod.getGenericReturnType().toString();
+                            if (!newType.equals(oldType)) {
+                                incompatibleChanges.add(className + '.' + methodName + lineSeparator
+                                        + "    (old) " + oldType + lineSeparator
+                                        + "    (new) " + newType + lineSeparator);
+                            }
+                        }
+                        if (parameterTypes.length == 0) {
+                            if (newMethod.isAnnotationPresent(Deprecated.class)) {
+                                methodChanges.put(methodName, Boolean.FALSE);
+                            } else {
+                                final Object uml = newMethod.getAnnotation(newUML);
+                                if (uml != null && oldMethod == null) {
+                                    methodChanges.put(methodName + ':' + newIdentifier.invoke(uml,
(Object[]) null), Boolean.TRUE);
+                                }
+                            }
+                        }
+                    }
+                    if (!methodChanges.isEmpty()) {
+                        out.append(className);
+                        char separator = '=';
+                        for (final Map.Entry<String,Boolean> entry : methodChanges.entrySet())
{
+                            out.append(separator).append(entry.getValue() ? '+' : '-').append(entry.getKey());
+                            separator = ' ';
+                        }
+                        out.append(lineSeparator);
+                    }
+                }
+            }
+        } finally {
+            newJARContent.close();
+        }
+        if (!incompatibleChanges.isEmpty()) {
+            out.append(lineSeparator)
+               .append("═════════════════════════════").append(lineSeparator)
+               .append("Incompatible changes detected").append(lineSeparator)
+               .append("═════════════════════════════").append(lineSeparator);
+            for (final String m : incompatibleChanges) {
+                out.append(m);
+            }
+        }
+    }
 }



Mime
View raw message