sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1778901 [1/4] - in /sis/trunk: ./ application/sis-console/src/main/java/org/apache/sis/console/ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src/main/java/org/apache/sis/metadata/ core/sis-metadata/sr...
Date Sun, 15 Jan 2017 10:36:24 GMT
Author: desruisseaux
Date: Sun Jan 15 10:36:23 2017
New Revision: 1778901

URL: http://svn.apache.org/viewvc?rev=1778901&view=rev
Log:
Merge SIS-346 work (support of class that implement more than one metadata interface) from JDK7 branch.

Added:
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java
      - copied unchanged from r1778899, sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java
Modified:
    sis/trunk/   (props changed)
    sis/trunk/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java
    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/Pruner.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/AbstractMetadataTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.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/PropertyInformationTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/PrunerTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeChildrenTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/ValueMapTest.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/ModifiableIdentifierMap.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/ObjectReference.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gml/GMLAdapter.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Citations.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/ComparisonMode.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/LenientComparable.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeNodeList.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTable.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/IdentifiedObject.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/IdentifierSpace.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/ReferenceResolver.java
    sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/collection/DefaultTreeTableTest.java
    sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java

Propchange: sis/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Sun Jan 15 10:36:23 2017
@@ -1,5 +1,5 @@
 /sis/branches/Android:1430670-1480699
 /sis/branches/JDK6:1394364-1758914
-/sis/branches/JDK7:1394913-1778499
-/sis/branches/JDK8:1584960-1778497
+/sis/branches/JDK7:1394913-1778899
+/sis/branches/JDK8:1584960-1778893
 /sis/branches/JDK9:1773327-1773512

Modified: sis/trunk/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java
URL: http://svn.apache.org/viewvc/sis/trunk/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java?rev=1778901&r1=1778900&r2=1778901&view=diff
==============================================================================
--- sis/trunk/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java [UTF-8] (original)
+++ sis/trunk/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java [UTF-8] Sun Jan 15 10:36:23 2017
@@ -51,7 +51,7 @@ import org.apache.sis.xml.XML;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.7
+ * @version 0.8
  * @module
  */
 class MetadataCommand extends CommandRunner {
@@ -195,7 +195,9 @@ class MetadataCommand extends CommandRun
     final void format(final Object object) throws IOException, JAXBException {
         switch (outputFormat) {
             case TEXT: {
-                final TreeTable tree = MetadataStandard.ISO_19115.asTreeTable(object, ValueExistencePolicy.NON_EMPTY);
+                final TreeTable tree = MetadataStandard.ISO_19115.asTreeTable(object,
+                        (object instanceof Metadata) ? Metadata.class : null,
+                        ValueExistencePolicy.NON_EMPTY);
                 final TreeTableFormat tf = new TreeTableFormat(locale, timezone);
                 tf.setColumns(TableColumn.NAME, TableColumn.VALUE);
                 tf.format(tree, out);

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java?rev=1778901&r1=1778900&r2=1778901&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java [UTF-8] Sun Jan 15 10:36:23 2017
@@ -180,7 +180,7 @@ public class Merger {
         if (source instanceof AbstractMetadata) {
             sourceMap = ((AbstractMetadata) source).asMap();          // Gives to subclasses a chance to override.
         } else {
-            sourceMap = standard.asValueMap(source, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
+            sourceMap = standard.asValueMap(source, null, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
         }
         /*
          * Iterate on source values in order to find the objects that need to be copied or merged.

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java?rev=1778901&r1=1778900&r2=1778901&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] Sun Jan 15 10:36:23 2017
@@ -71,7 +71,7 @@ import org.apache.sis.util.collection.Tr
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.5
+ * @version 0.8
  * @module
  *
  * @see MetadataStandard
@@ -98,9 +98,8 @@ public abstract class AbstractMetadata i
     public abstract MetadataStandard getStandard();
 
     /**
-     * Returns the metadata interface implemented by this class. It should be one of the
-     * interfaces defined in the {@linkplain #getStandard() metadata standard} implemented
-     * by this class.
+     * Returns the metadata interface implemented by this class. It should be one of the interfaces
+     * defined in the {@linkplain #getStandard() metadata standard} implemented by this class.
      *
      * @return the standard interface implemented by this implementation class.
      *
@@ -127,7 +126,7 @@ public abstract class AbstractMetadata i
      *
      * <div class="section">Note for implementors</div>
      * The default implementation uses Java reflection indirectly, by iterating over all entries
-     * returned by {@link MetadataStandard#asValueMap(Object, KeyNamePolicy, ValueExistencePolicy)}.
+     * returned by {@link MetadataStandard#asValueMap(Object, Class, KeyNamePolicy, ValueExistencePolicy)}.
      * Subclasses that override this method should usually not invoke {@code super.isEmpty()},
      * because the Java reflection will discover and process the properties defined in the
      * subclasses - which is usually not the intend when overriding a method.
@@ -147,7 +146,7 @@ public abstract class AbstractMetadata i
          */
         final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
         try {
-            return Pruner.isEmpty(this, true, false);
+            return Pruner.isEmpty(this, getInterface(), true, false);
         } finally {
             if (!allowNull) {
                 Semaphores.clear(Semaphores.NULL_COLLECTION);
@@ -166,7 +165,7 @@ public abstract class AbstractMetadata i
         // See comment in 'isEmpty()' about NULL_COLLECTION semaphore purpose.
         final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
         try {
-            Pruner.isEmpty(this, true, true);
+            Pruner.isEmpty(this, getInterface(), true, true);
         } finally {
             if (!allowNull) {
                 Semaphores.clear(Semaphores.NULL_COLLECTION);
@@ -204,15 +203,15 @@ public abstract class AbstractMetadata i
      * The default implementation is equivalent to the following method call:
      *
      * {@preformat java
-     *   return getStandard().asValueMap(this, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
+     *   return getStandard().asValueMap(this, null, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
      * }
      *
      * @return a view of this metadata object as a map.
      *
-     * @see MetadataStandard#asValueMap(Object, KeyNamePolicy, ValueExistencePolicy)
+     * @see MetadataStandard#asValueMap(Object, Class, KeyNamePolicy, ValueExistencePolicy)
      */
     public Map<String,Object> asMap() {
-        return getStandard().asValueMap(this, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
+        return getStandard().asValueMap(this, null, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
     }
 
     /**
@@ -272,15 +271,15 @@ public abstract class AbstractMetadata i
      * The default implementation is equivalent to the following method call:
      *
      * {@preformat java
-     *   return getStandard().asTreeTable(this, ValueExistencePolicy.NON_EMPTY);
+     *   return getStandard().asTreeTable(this, null, ValueExistencePolicy.NON_EMPTY);
      * }
      *
      * @return a tree table representation of the specified metadata.
      *
-     * @see MetadataStandard#asTreeTable(Object, ValueExistencePolicy)
+     * @see MetadataStandard#asTreeTable(Object, Class, ValueExistencePolicy)
      */
     public TreeTable asTreeTable() {
-        return getStandard().asTreeTable(this, ValueExistencePolicy.NON_EMPTY);
+        return getStandard().asTreeTable(this, null, ValueExistencePolicy.NON_EMPTY);
     }
 
     /**

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java?rev=1778901&r1=1778900&r2=1778901&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java [UTF-8] Sun Jan 15 10:36:23 2017
@@ -35,7 +35,7 @@ import org.opengis.annotation.UML;
  * @version 0.3
  * @module
  *
- * @see MetadataStandard#asValueMap(Object, KeyNamePolicy, ValueExistencePolicy)
+ * @see MetadataStandard#asValueMap(Object, Class, KeyNamePolicy, ValueExistencePolicy)
  */
 public enum KeyNamePolicy {
     /**

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=1778901&r1=1778900&r2=1778901&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] Sun Jan 15 10:36:23 2017
@@ -34,7 +34,6 @@ import org.opengis.referencing.Reference
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.internal.system.Modules;
@@ -96,7 +95,7 @@ import org.apache.sis.internal.jdk8.BiFu
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.5
+ * @version 0.8
  * @module
  *
  * @see AbstractMetadata
@@ -192,7 +191,7 @@ public class MetadataStandard implements
      *   <li>{@link PropertyAccessor} otherwise.</li>
      * </ul>
      */
-    private final transient ConcurrentMap<Class<?>, Object> accessors; // written by reflection on deserialization.
+    private final transient ConcurrentMap<CacheKey,Object> accessors;      // written by reflection on deserialization.
 
     /**
      * Creates a new instance working on implementation of interfaces defined in the specified package.
@@ -316,12 +315,12 @@ public class MetadataStandard implements
      * @throws ClassCastException if the specified class does not implement a metadata interface
      *         of the expected package and {@code mandatory} is {@code true}.
      */
-    final PropertyAccessor getAccessor(final Class<?> implementation, final boolean mandatory) {
+    final PropertyAccessor getAccessor(final CacheKey key, final boolean mandatory) {
         /*
          * 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);
+        final Object value = accessors.get(key);
         if (value instanceof PropertyAccessor) {
             return (PropertyAccessor) value;
         }
@@ -332,45 +331,47 @@ public class MetadataStandard implements
         final Class<?> type;
         if (value instanceof Class<?>) {
             type = (Class<?>) value;                            // Stored result of previous call to findInterface(…).
-            assert type == findInterface(implementation) : implementation;
-        } else {
+            assert type == findInterface(key) : key;
+        } else if (key.isValid()) {
             /*
              * Nothing was computed, we need to start from scratch. The first step is to find
              * the interface implemented by the given class. If we can not find an interface,
              * we will delegate to the dependencies and store the result for avoiding redoing
              * this search next time.
              */
-            type = findInterface(implementation);
+            type = findInterface(key);
             if (type == null) {
                 if (dependencies != null) {
                     for (final MetadataStandard dependency : dependencies) {
-                        final PropertyAccessor accessor = dependency.getAccessor(implementation, false);
+                        final PropertyAccessor accessor = dependency.getAccessor(key, false);
                         if (accessor != null) {
-                            accessors.put(implementation, accessor);        // Ok to overwrite existing instance here.
+                            accessors.put(key, accessor);               // Ok to overwrite existing instance here.
                             return accessor;
                         }
                     }
                 }
                 if (mandatory) {
-                    throw new ClassCastException(Errors.format(Errors.Keys.UnknownType_1, implementation));
+                    throw new ClassCastException(key.unrecognized());
                 }
                 return null;
             }
+        } else {
+            throw new ClassCastException(key.invalid());
         }
         /*
          * Found the interface for which to create an accessor. Creates the accessor now, unless an accessor
          * has been created concurrently in another thread in which case the later will be returned.
          */
-        return (PropertyAccessor) JDK8.compute(accessors, implementation, new BiFunction<Class<?>, Object, Object>() {
-            @Override public Object apply(final Class<?> k, final Object v) {
+        return (PropertyAccessor) JDK8.compute(accessors, key, new BiFunction<CacheKey, Object, Object>() {
+            @Override public Object apply(final CacheKey k, final Object v) {
                 if (v instanceof PropertyAccessor) {
                     return v;
                 }
                 final PropertyAccessor accessor;
                 if (SpecialCases.isSpecialCase(type)) {
-                    accessor = new SpecialCases(citation, type, implementation);
+                    accessor = new SpecialCases(citation, type, key.type);
                 } else {
-                    accessor = new PropertyAccessor(citation, type, implementation);
+                    accessor = new PropertyAccessor(citation, type, key.type);
                 }
                 return accessor;
             }
@@ -387,34 +388,45 @@ public class MetadataStandard implements
      *         or implements an interface of this standard.
      */
     public boolean isMetadata(final Class<?> type) {
-        if (type != null) {
-            if (accessors.containsKey(type)) {
-                return true;
-            }
-            if (dependencies != null) {
-                for (final MetadataStandard dependency : dependencies) {
-                    if (dependency.isMetadata(type)) {
-                        accessors.putIfAbsent(type, dependency);
-                        return true;
-                    }
+        return (type != null) && isMetadata(new CacheKey(type));
+    }
+
+    /**
+     * Implementation of {@link #isMetadata(Class)} with the possibility to specify the property type.
+     * We do not provide the additional functionality of this method in public API on the assumption
+     * that if the user know the base metadata type implemented by the value, then (s)he already know
+     * that the value is a metadata instance.
+     *
+     * @see #getInterface(CacheKey)
+     */
+    private boolean isMetadata(final CacheKey key) {
+        assert key.isValid() : key;
+        if (accessors.containsKey(key)) {
+            return true;
+        }
+        if (dependencies != null) {
+            for (final MetadataStandard dependency : dependencies) {
+                if (dependency.isMetadata(key)) {
+                    accessors.putIfAbsent(key, dependency);
+                    return true;
                 }
             }
-            /*
-             * At this point, all cached values (including those in dependencies) have been checked.
-             * Performs the 'findInterface' computation only in last resort. Current implementation
-             * does not store negative results in order to avoid filling the cache with unrelated classes.
-             */
-            final Class<?> standardType = findInterface(type);
-            if (standardType != null) {
-                accessors.putIfAbsent(type, standardType);
-                return true;
-            }
+        }
+        /*
+         * At this point, all cached values (including those in dependencies) have been checked.
+         * Performs the 'findInterface' computation only in last resort. Current implementation
+         * does not store negative results in order to avoid filling the cache with unrelated classes.
+         */
+        final Class<?> standardType = findInterface(key);
+        if (standardType != null) {
+            accessors.putIfAbsent(key, standardType);
+            return true;
         }
         return false;
     }
 
     /**
-     * Returns {@code true} if the given implementation class, normally rejected by {@link #findInterface(Class)},
+     * Returns {@code true} if the given implementation class, normally rejected by {@link #findInterface(CacheKey)},
      * should be accepted as a pseudo-interface. We use this undocumented feature when Apache SIS experiments a new
      * API which is not yet published in GeoAPI. This happen for example when upgrading Apache SIS public API from
      * the ISO 19115:2003 standard to the ISO 19115:2014 version, but GeoAPI interfaces are still the old version.
@@ -430,59 +442,64 @@ public class MetadataStandard implements
      * Only one metadata interface can be implemented. If the given type is already
      * an interface from the standard, then it is returned directly.
      *
+     * <p>If the given class is the return value of a property, then the type of that property should be specified
+     * in the {@code key.propertyType} argument. This information allows this method to take in account only types
+     * that are assignable to {@code propertyType}, so we can handle classes that implement many metadata interfaces.
+     * For example the {@link org.apache.sis.internal.simple} package have various examples of implementing more than
+     * one interface for convenience.</p>
+     *
      * <p>This method ignores dependencies. Fallback on metadata standard dependencies shall be done by the caller.</p>
      *
-     * @param  type  the standard interface or the implementation class.
+     * @param  key  the standard interface or the implementation class.
      * @return the single interface, or {@code null} if none where found.
      */
-    private Class<?> findInterface(final Class<?> type) {
-        if (type != null) {
-            if (type.isInterface()) {
-                if (isSupported(type.getName())) {
-                    return type;
+    private Class<?> findInterface(final CacheKey key) {
+        assert key.isValid() : key;
+        if (key.type.isInterface()) {
+            if (isSupported(key.type.getName())) {
+                return key.type;
+            }
+        } else {
+            /*
+             * Gets every interfaces from the supplied package in declaration order,
+             * including the ones declared in the super-class.
+             */
+            final Set<Class<?>> interfaces = new LinkedHashSet<>();
+            for (Class<?> t=key.type; t!=null; t=t.getSuperclass()) {
+                getInterfaces(t, key.propertyType, interfaces);
+            }
+            /*
+             * If we found more than one interface, removes the
+             * ones that are sub-interfaces of the other.
+             */
+            for (final Iterator<Class<?>> it=interfaces.iterator(); it.hasNext();) {
+                final Class<?> candidate = it.next();
+                for (final Class<?> child : interfaces) {
+                    if (candidate != child && candidate.isAssignableFrom(child)) {
+                        it.remove();
+                        break;
+                    }
                 }
-            } else {
-                /*
-                 * Gets every interfaces from the supplied package in declaration order,
-                 * including the ones declared in the super-class.
-                 */
-                final Set<Class<?>> interfaces = new LinkedHashSet<>();
-                for (Class<?> t=type; t!=null; t=t.getSuperclass()) {
-                    getInterfaces(t, interfaces);
+            }
+            final Iterator<Class<?>> it = interfaces.iterator();
+            if (it.hasNext()) {
+                final Class<?> candidate = it.next();
+                if (!it.hasNext()) {
+                    return candidate;
                 }
+                // Found more than one interface; we don't know which one to pick.
+                // Returns 'null' for now; the caller will thrown an exception.
+            } else if (IMPLEMENTATION_CAN_ALTER_API && isPendingAPI(key.type)) {
                 /*
-                 * If we found more than one interface, removes the
-                 * ones that are sub-interfaces of the other.
+                 * Found no interface. According to our method contract we should return null.
+                 * However we make an exception if the implementation class has a UML annotation.
+                 * The reason is that when upgrading  API  from ISO 19115:2003 to ISO 19115:2014,
+                 * implementations are provided in Apache SIS before the corresponding interfaces
+                 * are published on GeoAPI. The reason why GeoAPI is slower to upgrade is that we
+                 * have to go through a voting process inside the Open Geospatial Consortium (OGC).
+                 * So we use those implementation classes as a temporary substitute for the interfaces.
                  */
-                for (final Iterator<Class<?>> it=interfaces.iterator(); it.hasNext();) {
-                    final Class<?> candidate = it.next();
-                    for (final Class<?> child : interfaces) {
-                        if (candidate != child && candidate.isAssignableFrom(child)) {
-                            it.remove();
-                            break;
-                        }
-                    }
-                }
-                final Iterator<Class<?>> it = interfaces.iterator();
-                if (it.hasNext()) {
-                    final Class<?> candidate = it.next();
-                    if (!it.hasNext()) {
-                        return candidate;
-                    }
-                    // Found more than one interface; we don't know which one to pick.
-                    // Returns 'null' for now; the caller will thrown an exception.
-                } else if (IMPLEMENTATION_CAN_ALTER_API && isPendingAPI(type)) {
-                    /*
-                     * Found no interface. According to our method contract we should return null.
-                     * However we make an exception if the implementation class has a UML annotation.
-                     * The reason is that when upgrading  API  from ISO 19115:2003 to ISO 19115:2014,
-                     * implementations are provided in Apache SIS before the corresponding interfaces
-                     * are published on GeoAPI. The reason why GeoAPI is slower to upgrade is that we
-                     * have to go through a voting process inside the Open Geospatial Consortium (OGC).
-                     * So we use those implementation classes as a temporary substitute for the interfaces.
-                     */
-                    return type;
-                }
+                return key.type;
             }
         }
         return null;
@@ -492,14 +509,22 @@ public class MetadataStandard implements
      * Puts every interfaces for the given type in the specified collection.
      * This method invokes itself recursively for scanning parent interfaces.
      *
+     * <p>If the given class is the return value of a property, then the type of that property should be specified
+     * in the {@code propertyType} argument. This information allows this method to take in account only the types
+     * that are assignable to {@code propertyType}, so we can handle classes that implement many metadata interfaces.
+     * For example the {@link org.apache.sis.internal.simple} package have various examples of implementing more than
+     * one interface for convenience.</p>
+     *
      * @see Classes#getAllInterfaces(Class)
      */
-    private void getInterfaces(final Class<?> type, final Collection<Class<?>> interfaces) {
+    private void getInterfaces(final Class<?> type, final Class<?> propertyType, final Collection<Class<?>> interfaces) {
         for (final Class<?> candidate : type.getInterfaces()) {
-            if (isSupported(candidate.getName())) {
-                interfaces.add(candidate);
+            if (propertyType.isAssignableFrom(candidate)) {
+                if (isSupported(candidate.getName())) {
+                    interfaces.add(candidate);
+                }
+                getInterfaces(candidate, propertyType, interfaces);
             }
-            getInterfaces(candidate, interfaces);
         }
     }
 
@@ -521,34 +546,49 @@ public class MetadataStandard implements
      *
      * @see AbstractMetadata#getInterface()
      */
-    @SuppressWarnings("unchecked")
     public <T> Class<? super T> getInterface(final Class<T> type) throws ClassCastException {
         ensureNonNull("type", type);
+        return getInterface(new CacheKey(type));
+    }
+
+    /**
+     * Implementation of {@link #getInterface(Class)} with the possibility to specify the property type.
+     * We do not provide the additional functionality of this method in public API on the assumption that
+     * users who want to invoke a {@code getInterface(…)} method does not know what that interface is.
+     * In Apache SIS case, we invoke this method when we almost know what the interface is but want to
+     * check if the actual value is a subtype.
+     *
+     * @see #isMetadata(CacheKey)
+     */
+    @SuppressWarnings("unchecked")
+    final <T> Class<? super T> getInterface(final CacheKey key) throws ClassCastException {
         final Class<?> interf;
-        final Object value = accessors.get(type);
+        final Object value = accessors.get(key);
         if (value instanceof PropertyAccessor) {
             interf = ((PropertyAccessor) value).type;
         } else if (value instanceof Class<?>) {
             interf = (Class<?>) value;
         } else if (value instanceof MetadataStandard) {
-            interf = ((MetadataStandard) value).getInterface(type);
-        } else {
-            interf = findInterface(type);
+            interf = ((MetadataStandard) value).getInterface(key);
+        } else if (key.isValid()) {
+            interf = findInterface(key);
             if (interf != null) {
-                accessors.putIfAbsent(type, interf);
+                accessors.putIfAbsent(key, interf);
             } else {
                 if (dependencies != null) {
                     for (final MetadataStandard dependency : dependencies) {
-                        if (dependency.isMetadata(type)) {
-                            accessors.putIfAbsent(type, dependency);
-                            return dependency.getInterface(type);
+                        if (dependency.isMetadata(key)) {
+                            accessors.putIfAbsent(key, dependency);
+                            return dependency.getInterface(key);
                         }
                     }
                 }
-                throw new ClassCastException(Errors.format(Errors.Keys.UnknownType_1, type));
+                throw new ClassCastException(key.unrecognized());
             }
+        } else {
+            throw new ClassCastException(key.invalid());
         }
-        assert interf.isAssignableFrom(type) : type;
+        assert interf.isAssignableFrom(key.type) : key;
         return (Class<? super T>) interf;
     }
 
@@ -605,7 +645,7 @@ public class MetadataStandard implements
         if (implementation != null) {
             type = implementation;
         }
-        return new NameMap(getAccessor(type, true), keyPolicy, valuePolicy);
+        return new NameMap(getAccessor(new CacheKey(type), true), keyPolicy, valuePolicy);
     }
 
     /**
@@ -643,7 +683,7 @@ public class MetadataStandard implements
         if (implementation != null) {
             type = implementation;
         }
-        return new TypeMap(getAccessor(type, true), keyPolicy, valuePolicy);
+        return new TypeMap(getAccessor(new CacheKey(type), true), keyPolicy, valuePolicy);
     }
 
     /**
@@ -699,7 +739,7 @@ public class MetadataStandard implements
         if (implementation != null) {
             type = implementation;
         }
-        return new InformationMap(getAccessor(type, true), keyPolicy);
+        return new InformationMap(getAccessor(new CacheKey(type), true), keyPolicy);
     }
 
     /**
@@ -735,21 +775,50 @@ public class MetadataStandard implements
      * values, then make sure that the given value is a collection when the associated metadata property expects
      * such collection.
      *
+     * <div class="section">Disambiguating instances that implement more than one metadata interface</div>
+     * It is some time convenient to implement more than one interface by the same class.
+     * For example an implementation interested only in extents defined by geographic bounding boxes could implement
+     * {@link org.opengis.metadata.extent.Extent} and {@link org.opengis.metadata.extent.GeographicBoundingBox}
+     * by the same class. In such case, it is necessary to tell to this method which one of those two interfaces
+     * shall be reflected in the returned map. This information can be provided by the {@code baseType} argument.
+     * That argument needs to be non-null only in situations where an ambiguity can arise; {@code baseType} can be null
+     * if the given metadata implements only one interface recognized by this {@code MetadataStandard} instance.
+     *
      * @param  metadata     the metadata object to view as a map.
+     * @param  baseType     base type of the metadata of interest, or {@code null} if unspecified.
      * @param  keyPolicy    determines the string representation of map keys.
      * @param  valuePolicy  whether the entries having null value or empty collection shall be included in the map.
      * @return a map view over the metadata object.
      * @throws ClassCastException if the metadata object does not implement a metadata interface of the expected package.
      *
      * @see AbstractMetadata#asMap()
+     *
+     * @since 0.8
      */
-    public Map<String,Object> asValueMap(final Object metadata, final KeyNamePolicy keyPolicy,
-            final ValueExistencePolicy valuePolicy) throws ClassCastException
+    public Map<String,Object> asValueMap(final Object metadata, final Class<?> baseType,
+            final KeyNamePolicy keyPolicy, final ValueExistencePolicy valuePolicy) throws ClassCastException
     {
         ensureNonNull("metadata",    metadata);
         ensureNonNull("keyPolicy",   keyPolicy);
         ensureNonNull("valuePolicy", valuePolicy);
-        return new ValueMap(metadata, getAccessor(metadata.getClass(), true), keyPolicy, valuePolicy);
+        return new ValueMap(metadata, getAccessor(new CacheKey(metadata.getClass(), baseType), true), keyPolicy, valuePolicy);
+    }
+
+    /**
+     * @deprecated Replaced by {@link #asValueMap(Object, Class, KeyNamePolicy, ValueExistencePolicy)}
+     * (i.e. a {@code Class} argument has been inserted after the metadata value).
+     *
+     * @param  metadata     the metadata object to view as a map.
+     * @param  keyPolicy    determines the string representation of map keys.
+     * @param  valuePolicy  whether the entries having null value or empty collection shall be included in the map.
+     * @return a map view over the metadata object.
+     * @throws ClassCastException if the metadata object does not implement a metadata interface of the expected package.
+     */
+    @Deprecated
+    public Map<String,Object> asValueMap(final Object metadata, final KeyNamePolicy keyPolicy,
+            final ValueExistencePolicy valuePolicy) throws ClassCastException
+    {
+        return asValueMap(metadata, null, keyPolicy, valuePolicy);
     }
 
     /**
@@ -807,19 +876,44 @@ public class MetadataStandard implements
      * Note that whether the child appears as effectively removed from the node or just cleared
      * (i.e. associated to a null value) depends on the {@code valuePolicy} argument.
      *
+     * <div class="section">Disambiguating instances that implement more than one metadata interface</div>
+     * If the given {@code metadata} instance implements more than one interface recognized by this
+     * {@code MetadataStandard}, then the {@code baseType} argument need to be non-null in order to
+     * specify which interface to reflect in the tree.
+     *
      * @param  metadata     the metadata object to view as a tree table.
+     * @param  baseType     base type of the metadata of interest, or {@code null} if unspecified.
      * @param  valuePolicy  whether the property having null value or empty collection shall be included in the tree.
      * @return a tree table representation of the specified metadata.
      * @throws ClassCastException if the metadata object does not implement a metadata interface of the expected package.
      *
      * @see AbstractMetadata#asTreeTable()
+     *
+     * @since 0.8
      */
-    public TreeTable asTreeTable(final Object metadata, final ValueExistencePolicy valuePolicy)
+    public TreeTable asTreeTable(final Object metadata, Class<?> baseType, final ValueExistencePolicy valuePolicy)
             throws ClassCastException
     {
         ensureNonNull("metadata",    metadata);
         ensureNonNull("valuePolicy", valuePolicy);
-        return new TreeTableView(this, metadata, valuePolicy);
+        if (baseType == null) {
+            baseType = getInterface(metadata.getClass());
+        }
+        return new TreeTableView(this, metadata, baseType, valuePolicy);
+    }
+
+    /**
+     * @deprecated Replaced by {@link #asTreeTable(Object, Class, ValueExistencePolicy)}
+     * (i.e. a {@code Class} argument has been inserted after the metadata value).
+     *
+     * @param  metadata     the metadata object to view as a tree table.
+     * @param  valuePolicy  whether the property having null value or empty collection shall be included in the tree.
+     * @return a tree table representation of the specified metadata.
+     * @throws ClassCastException if the metadata object does not implement a metadata interface of the expected package.
+     */
+    @Deprecated
+    public TreeTable asTreeTable(final Object metadata, final ValueExistencePolicy valuePolicy) throws ClassCastException {
+        return asTreeTable(metadata, null, valuePolicy);
     }
 
     /**
@@ -832,7 +926,7 @@ public class MetadataStandard implements
      * @see ModifiableMetadata#freeze()
      */
     final void freeze(final Object metadata) throws ClassCastException {
-        getAccessor(metadata.getClass(), true).freeze(metadata);
+        getAccessor(new CacheKey(metadata.getClass()), true).freeze(metadata);
     }
 
     /**
@@ -872,8 +966,10 @@ public class MetadataStandard implements
         if (type1 != type2 && mode == ComparisonMode.STRICT) {
             return false;
         }
-        final PropertyAccessor accessor = getAccessor(type1, true);
-        if (type1 != type2 && (!accessor.type.isAssignableFrom(type2) || accessor.type != getAccessor(type2, false).type)) {
+        final PropertyAccessor accessor = getAccessor(new CacheKey(type1), true);
+        if (type1 != type2 && (!accessor.type.isAssignableFrom(type2)
+                || accessor.type != getAccessor(new CacheKey(type2), false).type))
+        {
             /*
              * Note: the check for (accessor.type != getAccessor(…).type) would have been enough, but checking
              * for isAssignableFrom(…) first can avoid the (relatively costly) creation of new PropertyAccessor.
@@ -933,7 +1029,7 @@ public class MetadataStandard implements
                 // See comment in 'equals(…) about NULL_COLLECTION semaphore purpose.
                 final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
                 try {
-                    return getAccessor(metadata.getClass(), true).hashCode(metadata);
+                    return getAccessor(new CacheKey(metadata.getClass()), true).hashCode(metadata);
                 } finally {
                     inProgress.remove(metadata);
                     if (!allowNull) {

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=1778901&r1=1778900&r2=1778901&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] Sun Jan 15 10:36:23 2017
@@ -235,9 +235,9 @@ class PropertyAccessor {
     /**
      * Creates a new property accessor for the specified metadata implementation.
      *
-     * @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  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.
      */
     PropertyAccessor(final Citation standard, final Class<?> type, final Class<?> implementation) {
         assert type.isAssignableFrom(implementation) : implementation;
@@ -398,9 +398,9 @@ class PropertyAccessor {
      * Returns the getters. The returned array should never be modified,
      * since it may be shared among many instances of {@code PropertyAccessor}.
      *
-     * @param  type The metadata interface.
-     * @param  implementation The class of metadata implementations, or {@code type} if none.
-     * @return The getters declared in the given interface (never {@code null}).
+     * @param  type            the metadata interface.
+     * @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) {
         /*
@@ -492,10 +492,10 @@ class PropertyAccessor {
      * Returns the index of the specified property, or -1 if none.
      * The search is case-insensitive.
      *
-     * @param  name The name of the property to search.
-     * @param  mandatory Whether this method shall throw an exception or return {@code -1}
+     * @param  name       the name of the property to search.
+     * @param  mandatory  whether this method shall throw an exception or return {@code -1}
      *         if the given name is not found.
-     * @return The index of the given name, or -1 if none and {@code mandatory} is {@code false}.
+     * @return the index of the given name, or -1 if none and {@code mandatory} is {@code false}.
      * @throws IllegalArgumentException if the name is not found and {@code mandatory} is {@code true}.
      */
     final int indexOf(final String name, final boolean mandatory) {
@@ -521,9 +521,9 @@ class PropertyAccessor {
     /**
      * Returns the name of the property at the given index, or {@code null} if none.
      *
-     * @param  index The index of the property for which to get the name.
-     * @param  keyPolicy The kind of name to return.
-     * @return The name of the given kind at the given index, or {@code null} if the index is out of bounds.
+     * @param  index      the index of the property for which to get the name.
+     * @param  keyPolicy  the kind of name to return.
+     * @return the name of the given kind at the given index, or {@code null} if the index is out of bounds.
      */
     @SuppressWarnings("fallthrough")
     @Workaround(library="JDK", version="1.7") // Actually apply to String.intern() below.
@@ -567,9 +567,9 @@ class PropertyAccessor {
      *   <li>If the property is a collection, then returns the type of collection elements.</li>
      * </ul>
      *
-     * @param  index The index of the property.
-     * @param  policy The kind of type to return.
-     * @return The type of property values, or {@code null} if unknown.
+     * @param  index   the index of the property.
+     * @param  policy  the kind of type to return.
+     * @return the type of property values, or {@code null} if unknown.
      */
     Class<?> type(final int index, final TypeValuePolicy policy) {
         if (index >= 0 && index < allCount) {
@@ -623,8 +623,8 @@ class PropertyAccessor {
      * Returns the information for the property at the given index.
      * The information are created when first needed.
      *
-     * @param  index The index of the property for which to get the information.
-     * @return The information for the property at the given index, or {@code null} if the index is out of bounds.
+     * @param  index  the index of the property for which to get the information.
+     * @return the information for the property at the given index, or {@code null} if the index is out of bounds.
      *
      * @see PropertyInformation
      */
@@ -646,8 +646,10 @@ class PropertyAccessor {
             try {
                 range = implementation.getMethod(getter.getName(), (Class<?>[]) null).getAnnotation(ValueRange.class);
             } catch (NoSuchMethodException error) {
-                // Should never happen, since the implementation class
-                // implements the interface where the getter come from.
+                /*
+                 * Should never happen, since the implementation class
+                 * implements the interface where the getter come from.
+                 */
                 throw new AssertionError(error);
             }
             information = new PropertyInformation<>(standard, name, getter, elementType, range);
@@ -669,10 +671,10 @@ class PropertyAccessor {
      * so it is safe to invoke this method even if {@link #indexOf(String, boolean)}
      * returned -1.
      *
-     * @param  index The index of the property for which to get a value.
-     * @param  metadata The metadata object to query.
-     * @return The value, or {@code null} if none or if the given is out of bounds.
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * @param  index     the index of the property for which to get a value.
+     * @param  metadata  the metadata object to query.
+     * @return the value, or {@code null} if none or if the given is out of bounds.
+     * @throws BackingStoreException if the implementation threw a checked exception.
      */
     Object get(final int index, final Object metadata) throws BackingStoreException {
         return (index >= 0 && index < allCount) ? get(getters[index], metadata) : null;
@@ -684,9 +686,9 @@ class PropertyAccessor {
      * However if a checked exception is throw anyway (maybe in user defined "standard"), it
      * will be wrapped in a {@link BackingStoreException}. Unchecked exceptions are propagated.
      *
-     * @param  method The method to use for the query.
-     * @param  metadata The metadata object to query.
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * @param  method    the method to use for the query.
+     * @param  metadata  the metadata object to query.
+     * @throws BackingStoreException if the implementation threw a checked exception.
      *
      * @see #set(Method, Object, Object[])
      */
@@ -735,12 +737,12 @@ class PropertyAccessor {
      * However the given value will be silently discarded, so index out-of-bounds shall be used only
      * in the context of {@code remove} operations (this is not verified).</p>
      *
-     * @param  index       The index of the property to set.
-     * @param  metadata    The metadata object on which to set the value.
-     * @param  value       The new value.
-     * @param  mode        Whether this method should first fetches the old value,
-     *                     as one of the {@code RETURN_*} constants.
-     * @return The old value, or {@code null} if {@code returnValue} was {@code RETURN_NULL}.
+     * @param  index     the index of the property to set.
+     * @param  metadata  the metadata object on which to set the value.
+     * @param  value     the new value.
+     * @param  mode      whether this method should first fetches the old value,
+     *                   as one of the {@code RETURN_*} constants.
+     * @return the old value, or {@code null} if {@code returnValue} was {@code RETURN_NULL}.
      * @throws UnmodifiableMetadataException if the property for the given key is read-only.
      * @throws ClassCastException if the given value is not of the expected type.
      * @throws BackingStoreException if the implementation threw a checked exception.
@@ -756,7 +758,7 @@ class PropertyAccessor {
             final Method setter = setters[index];
             if (setter != null) {
                 final Object oldValue;
-                final Object snapshot; // Copy of oldValue before modification.
+                final Object snapshot;                      // Copy of oldValue before modification.
                 switch (mode) {
                     case RETURN_NULL: {
                         oldValue = null;
@@ -822,10 +824,10 @@ class PropertyAccessor {
      * exception is throw anyway, then it will be wrapped in a {@link BackingStoreException}.
      * Unchecked exceptions are propagated.</p>
      *
-     * @param  setter    The method to use for setting the new value.
-     * @param  metadata  The metadata object to query.
-     * @param  newValues The argument to give to the method to be invoked.
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * @param  setter     the method to use for setting the new value.
+     * @param  metadata   the metadata object to query.
+     * @param  newValues  the argument to give to the method to be invoked.
+     * @throws BackingStoreException if the implementation threw a checked exception.
      *
      * @see #get(Method, Object)
      */
@@ -873,21 +875,21 @@ class PropertyAccessor {
      * those collections are live. However this method can be though as if the collections were
      * not live, since the caller will invoke the setter method with the collection anyway.
      *
-     * @param getter      The method to use for fetching the previous value.
-     * @param metadata    The metadata object to query and modify.
-     * @param oldValue    The value returned by {@code get(getter, metadata)}, or {@code null} if unknown.
-     *                    This parameter is only an optimization for avoiding to invoke the getter method
-     *                    twice if the value is already known.
-     * @param newValues   The argument to convert. The content of this array will be modified in-place.
-     *                    Current implementation requires an array of length 1, however this restriction
-     *                    may be relaxed in a future SIS version if needed.
-     * @param elementType The target type (if singleton) or the type of elements in the collection.
-     * @param append      If {@code true} and the value is a collection, then that collection will be added
-     *                    to any previously existing collection instead of replacing it.
-     * @return If the given value has been added to an existing collection, then whether that existing
+     * @param  getter       the method to use for fetching the previous value.
+     * @param  metadata     the metadata object to query and modify.
+     * @param  oldValue     the value returned by {@code get(getter, metadata)}, or {@code null} if unknown.
+     *                      This parameter is only an optimization for avoiding to invoke the getter method
+     *                      twice if the value is already known.
+     * @param  newValues    the argument to convert. The content of this array will be modified in-place.
+     *                      Current implementation requires an array of length 1, however this restriction
+     *                      may be relaxed in a future SIS version if needed.
+     * @param  elementType  the target type (if singleton) or the type of elements in the collection.
+     * @param  append       if {@code true} and the value is a collection, then that collection will be added
+     *                      to any previously existing collection instead of replacing it.
+     * @return if the given value has been added to an existing collection, then whether that existing
      *         collection has been modified as a result of this method call. Otherwise {@code null}.
      * @throws ClassCastException if the element of the {@code arguments} array is not of the expected type.
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * @throws BackingStoreException if the implementation threw a checked exception.
      */
     private Boolean convert(final Method getter, final Object metadata, Object oldValue, final Object[] newValues,
             Class<?> elementType, final boolean append) throws ClassCastException, BackingStoreException
@@ -914,16 +916,18 @@ class PropertyAccessor {
              */
             if (newValue instanceof Collection<?>) {
                 final Iterator<?> it = ((Collection<?>) newValue).iterator();
-                if (!it.hasNext()) { // If empty, process like null argument.
+                if (!it.hasNext()) {                            // If empty, process like null argument.
                     newValues[0] = null;
                     return null;
                 }
                 final Object next = it.next();
-                if (!it.hasNext()) { // Singleton
+                if (!it.hasNext()) {                            // Singleton
                     newValue = next;
                 }
-                // Other cases: let the collection unchanged. It is likely to
-                // cause an exception later. The message should be appropriate.
+                /*
+                 * Other cases: let the collection unchanged. It is likely to
+                 * cause an exception later. The message should be appropriate.
+                 */
             }
             targetType = Numbers.primitiveToWrapper(targetType);
         } else {
@@ -944,8 +948,8 @@ class PropertyAccessor {
              */
             final boolean isCollection = (newValue instanceof Collection<?>);
             final Object[] elements = isCollection ? ((Collection<?>) newValue).toArray() : new Object[] {newValue};
-            final List<Object> elementList = Arrays.asList(elements); // Converted later (see above comment).
-            newValue = elementList; // Still contains the same values, but now guaranteed to be a collection.
+            final List<Object> elementList = Arrays.asList(elements);         // Converted later (see above comment).
+            newValue = elementList;         // Still contains the same values, but now guaranteed to be a collection.
             Collection<?> addTo = null;
             if (!isCollection || append) {
                 if (oldValue == null) {
@@ -996,9 +1000,9 @@ class PropertyAccessor {
      * The array content is modified in-place. This method accepts an array instead than
      * a single value because the values to convert may be the content of a collection.
      *
-     * @param  elements   The array which contains element to convert.
-     * @param  targetType The base type of target elements.
-     * @throws ClassCastException If an element can't be converted.
+     * @param  elements    the array which contains element to convert.
+     * @param  targetType  the base type of target elements.
+     * @throws ClassCastException if an element can't be converted.
      */
     @SuppressWarnings({"unchecked","rawtypes"})
     private void convert(final Object[] elements, final Class<?> targetType) throws ClassCastException {
@@ -1010,10 +1014,12 @@ class PropertyAccessor {
                 final Class<?> sourceType = value.getClass();
                 if (!targetType.isAssignableFrom(sourceType)) try {
                     if (converter == null) {
-                        converter = lastConverter; // Volatile field - read only if needed.
+                        converter = lastConverter;              // Volatile field - read only if needed.
                     }
-                    // Require the exact same classes, not parent or subclass,
-                    // otherwise the converter could be stricter than necessary.
+                    /*
+                     * Require the exact same classes, not parent or subclass,
+                     * otherwise the converter could be stricter than necessary.
+                     */
                     if (converter == null || converter.getSourceClass() != sourceType
                                           || converter.getTargetClass() != targetType)
                     {
@@ -1028,7 +1034,7 @@ class PropertyAccessor {
             }
         }
         if (hasNewConverter) {
-            lastConverter = converter; // Volatile field - store only if needed.
+            lastConverter = converter;                          // Volatile field - store only if needed.
         }
     }
 
@@ -1044,9 +1050,9 @@ class PropertyAccessor {
      *                      properties returned by {@link Collection#size()}.</li>
      * </ul>
      *
-     * @param  mode Kinds of count, as described above.
-     * @param  valuePolicy The behavior of the count toward null or empty values.
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * @param  mode         kinds of count, as described above.
+     * @param  valuePolicy  the behavior of the count toward null or empty values.
+     * @throws BackingStoreException if the implementation threw a checked exception.
      *
      * @see #count()
      */
@@ -1058,7 +1064,8 @@ class PropertyAccessor {
             return count();
         }
         int count = 0;
-        for (int i=0; i<standardCount; i++) { // Use 'standardCount' instead of 'allCount' for ignoring deprecated methods.
+        // Use 'standardCount' instead of 'allCount' for ignoring deprecated methods.
+        for (int i=0; i<standardCount; i++) {
             final Object value = get(getters[i], metadata);
             if (!valuePolicy.isSkipped(value)) {
                 switch (mode) {
@@ -1070,8 +1077,10 @@ class PropertyAccessor {
                         break;
                     }
                     case COUNT_DEEP: {
-                        // Count always at least one element because if the user wanted to skip null or empty
-                        // collections, then 'valuePolicy.isSkipped(value)' above would have returned 'true'.
+                        /*
+                         * Count always at least one element because if the user wanted to skip null or empty
+                         * collections, then 'valuePolicy.isSkipped(value)' above would have returned 'true'.
+                         */
                         count += (value != null && isCollection(i)) ? Math.max(((Collection<?>) value).size(), 1) : 1;
                         break;
                     }
@@ -1088,10 +1097,10 @@ class PropertyAccessor {
      * method without explicit calls to this {@code accessor.equals(…)} method for children.
      * However the final result may still be a deep comparison.
      *
-     * @param  metadata1 The first metadata object to compare. This object determines the accessor.
-     * @param  metadata2 The second metadata object to compare.
-     * @param  mode      The strictness level of the comparison.
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * @param  metadata1  the first metadata object to compare. This object determines the accessor.
+     * @param  metadata2  the second metadata object to compare.
+     * @param  mode       the strictness level of the comparison.
+     * @throws BackingStoreException if the implementation threw a checked exception.
      *
      * @see MetadataStandard#equals(Object, Object, ComparisonMode)
      */
@@ -1105,8 +1114,10 @@ class PropertyAccessor {
             final Object value1 = get(method, metadata1);
             final Object value2 = get(method, metadata2);
             if (isNullOrEmpty(value1) && isNullOrEmpty(value2)) {
-                // Consider empty collections/arrays as equal to null.
-                // Empty strings are also considered equal to null (this is more questionable).
+                /*
+                 * Consider empty collections/arrays as equal to null.
+                 * Empty strings are also considered equal to null (this is more questionable).
+                 */
                 continue;
             }
             final boolean equals;
@@ -1140,7 +1151,7 @@ class PropertyAccessor {
      * Replaces every properties in the specified metadata by their
      * {@linkplain ModifiableMetadata#unmodifiable() unmodifiable variant}.
      *
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * @throws BackingStoreException if the implementation threw a checked exception.
      */
     final void freeze(final Object metadata) throws BackingStoreException {
         assert implementation.isInstance(metadata) : metadata;
@@ -1191,7 +1202,7 @@ class PropertyAccessor {
      * This is a similar contract than {@link java.util.Set#hashCode()} (except for the interface)
      * and ensures that the hash code value is insensitive to the ordering of properties.
      *
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * @throws BackingStoreException if the implementation threw a checked exception.
      */
     public int hashCode(final Object metadata) throws BackingStoreException {
         assert type.isInstance(metadata) : metadata;

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java?rev=1778901&r1=1778900&r2=1778901&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java [UTF-8] Sun Jan 15 10:36:23 2017
@@ -31,7 +31,7 @@ import static org.apache.sis.metadata.Va
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 final class Pruner {
@@ -57,14 +57,8 @@ final class Pruner {
      * Returns the metadata properties. When used for pruning empty values, the map needs to
      * include empty (but non-null) values in order to allow us to set them to {@code null}.
      */
-    private static Map<String, Object> asMap(final MetadataStandard standard, final Object metadata,
-            final boolean mandatory, final boolean prune)
-    {
-        final PropertyAccessor accessor = standard.getAccessor(metadata.getClass(), mandatory);
-        if (accessor != null) {
-            return new ValueMap(metadata, accessor, KeyNamePolicy.JAVABEANS_PROPERTY, prune ? NON_NULL : NON_EMPTY);
-        }
-        return null;
+    private static Map<String, Object> asMap(final Object metadata, final PropertyAccessor accessor, final boolean prune) {
+        return new ValueMap(metadata, accessor, KeyNamePolicy.JAVABEANS_PROPERTY, prune ? NON_NULL : NON_EMPTY);
     }
 
     /**
@@ -84,22 +78,27 @@ final class Pruner {
      * It creates a map of visited nodes when the iteration begin, and deletes that map when the
      * iteration ends.</p>
      *
-     * @param  metadata  The metadata object.
-     * @param  mandatory {@code true} if we shall throw an exception if {@code metadata} is not of the expected class.
-     * @param  prune     {@code true} for deleting empty entries.
+     * @param  metadata      the metadata object.
+     * @param  mandatory     {@code true} if we shall throw an exception if {@code metadata} is not of the expected class.
+     * @param  propertyType  if the given metadata is the return value of a property, the declared type of that property.
+     * @param  prune         {@code true} for deleting empty entries.
      * @return {@code true} if all metadata properties are null or empty.
      */
-    static boolean isEmpty(final AbstractMetadata metadata, final boolean mandatory, final boolean prune) {
-        final Map<String,Object> properties = asMap(metadata.getStandard(), metadata, mandatory, prune);
-        if (properties == null) {
-            return false; // For metadata of unknown class, conservatively assume non-empty.
+    static boolean isEmpty(final AbstractMetadata metadata, final Class<?> propertyType,
+            final boolean mandatory, final boolean prune)
+    {
+        final PropertyAccessor accessor = metadata.getStandard().getAccessor(
+                new CacheKey(metadata.getClass(), propertyType), mandatory);
+        if (accessor == null) {
+            return false;                       // For metadata of unknown class, conservatively assume non-empty.
         }
+        final Map<String,Object> properties = asMap(metadata, accessor, prune);
         final Map<Object,Boolean> tested = MAPS.get();
         if (!tested.isEmpty()) {
-            return isEmpty(properties, tested, prune);
+            return isEmpty(accessor, properties, tested, prune);
         } else try {
             tested.put(metadata, Boolean.FALSE);
-            return isEmpty(properties, tested, prune);
+            return isEmpty(accessor, properties, tested, prune);
         } finally {
             MAPS.remove();
             /*
@@ -115,12 +114,13 @@ final class Pruner {
      * child metadata and optionally removing empty ones. The map given in argument is a safety
      * guard against infinite recursivity.
      *
-     * @param  properties The metadata properties.
-     * @param  tested An initially singleton map, to be filled with tested metadata.
-     * @param  prune {@code true} for removing empty properties.
+     * @param  accessor    the accessor that provided the metadata {@code properties}.
+     * @param  properties  the metadata properties.
+     * @param  tested      an initially singleton map, to be filled with tested metadata.
+     * @param  prune       {@code true} for removing empty properties.
      * @return {@code true} if all metadata properties are null or empty.
      */
-    private static boolean isEmpty(final Map<String,Object> properties,
+    private static boolean isEmpty(final PropertyAccessor accessor, final Map<String,Object> properties,
             final Map<Object,Boolean> tested, final boolean prune)
     {
         boolean isEmpty = true;
@@ -139,11 +139,11 @@ final class Pruner {
              */
             final Boolean isEntryEmpty = tested.put(value, Boolean.FALSE);
             if (isEntryEmpty != null) {
-                if (isEntryEmpty) { // If a value was already set, restore the original value.
+                if (isEntryEmpty) {                     // If a value was already set, restore the original value.
                     tested.put(value, Boolean.TRUE);
                 } else {
                     isEmpty = false;
-                    if (!prune) break; // No need to continue if we are not pruning the metadata.
+                    if (!prune) break;                  // No need to continue if we are not pruning the metadata.
                 }
             } else {
                 /*
@@ -151,6 +151,7 @@ final class Pruner {
                  * be a data object or a collection. For convenience we will proceed as if we had only
                  * collections, wrapping data object in a singleton collection if necessary.
                  */
+                Class<?> elementType = null;                    // To be computed when first needed.
                 boolean allElementsAreEmpty = true;
                 final Collection<?> values = CollectionsExt.toCollection(value);
                 for (final Iterator<?> it = values.iterator(); it.hasNext();) {
@@ -175,9 +176,24 @@ final class Pruner {
                         } else if (!(element instanceof Enum<?>) && !(element instanceof CodeList<?>)) {
                             final MetadataStandard standard = MetadataStandard.forClass(element.getClass());
                             if (standard != null) {
-                                isEmptyElement = isEmpty(asMap(standard, element, false, prune), tested, prune);
-                                if (!isEmptyElement && element instanceof Emptiable) {
-                                    isEmptyElement = ((Emptiable) element).isEmpty();
+                                /*
+                                 * For implementation that are not subtype of AbstractMetadata but nevertheless
+                                 * implement some metadata interface, we will invoke recursively this method.
+                                 * But since a class may implement more than one interface, we need to get the
+                                 * type of the value returned by the getter method in order to take in account
+                                 * only that type.
+                                 */
+                                if (elementType == null) {
+                                    elementType = accessor.type(accessor.indexOf(entry.getKey(), false),
+                                                                TypeValuePolicy.ELEMENT_TYPE);
+                                }
+                                final PropertyAccessor elementAccessor = standard.getAccessor(
+                                        new CacheKey(element.getClass(), elementType), false);
+                                if (elementAccessor != null) {
+                                    isEmptyElement = isEmpty(elementAccessor, asMap(element, elementAccessor, prune), tested, prune);
+                                    if (!isEmptyElement && element instanceof Emptiable) {
+                                        isEmptyElement = ((Emptiable) element).isEmpty();
+                                    }
                                 }
                             } else if (isPrimitive(entry)) {
                                 if (value instanceof Number) {

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java?rev=1778901&r1=1778900&r2=1778901&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java [UTF-8] Sun Jan 15 10:36:23 2017
@@ -20,6 +20,7 @@ import java.util.List;
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.NoSuchElementException;
 import java.util.ConcurrentModificationException;
 import org.apache.sis.util.Debug;
@@ -59,7 +60,7 @@ import org.apache.sis.util.resources.Voc
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 class TreeNode implements Node {
@@ -110,6 +111,14 @@ class TreeNode implements Node {
     final Object metadata;
 
     /**
+     * The return type of the getter method that provides the value encapsulated by this node.
+     * This information is used for filtering aspects when a class opportunistically implements
+     * many interfaces. This value is part of the {@link CacheKey} needed for invoking
+     * {@link MetadataStandard} methods.
+     */
+    final Class<?> baseType;
+
+    /**
      * The value of {@link TableColumn#NAME}, computed by {@link #getName()} then cached.
      *
      * @see #getName()
@@ -146,11 +155,13 @@ class TreeNode implements Node {
      *
      * @param  table     the table which is creating this root node.
      * @param  metadata  the root metadata object (can not be null).
+     * @param  baseType  the return type of the getter method that provides the value encapsulated by this node.
      */
-    TreeNode(final TreeTableView table, final Object metadata) {
+    TreeNode(final TreeTableView table, final Object metadata, final Class<?> baseType) {
         this.table    = table;
         this.parent   = null;
         this.metadata = metadata;
+        this.baseType = baseType;
     }
 
     /**
@@ -160,22 +171,25 @@ class TreeNode implements Node {
      *
      * @param  parent    the parent of this node.
      * @param  metadata  the metadata object for which this node will be a value.
+     * @param  baseType  the return type of the getter method that provides the value encapsulated by this node.
      */
-    TreeNode(final TreeNode parent, final Object metadata) {
+    TreeNode(final TreeNode parent, final Object metadata, final Class<?> baseType) {
         this.table    = parent.table;
         this.parent   = parent;
         this.metadata = metadata;
+        this.baseType = baseType;
+        if (!table.standard.isMetadata(baseType)) {
+            children = LEAF;
+        }
     }
 
     /**
-     * Must be invoked after construction. The work performed by this method can not be done
-     * in the {@code TreeNode} constructor, because it needs the subclasses to finish their
-     * construction first.
+     * Returns the key to use for calls to {@link MetadataStandard} methods.
+     * This key is used only for some default method implementations in the root node;
+     * children will use the class of their node value instead.
      */
-    final void init() {
-        if (!table.standard.isMetadata(getElementType())) {
-            children = LEAF;
-        }
+    private CacheKey key() {
+        return new CacheKey(metadata.getClass(), baseType);
     }
 
     /**
@@ -184,7 +198,7 @@ class TreeNode implements Node {
      * order to return the property identifier instead.
      */
     String getIdentifier() {
-        final Class<?> type = table.standard.getInterface(metadata.getClass());
+        final Class<?> type = table.standard.getInterface(key());
         final String id = Types.getStandardName(type);
         return (id != null) ? id : Classes.getShortName(type);
     }
@@ -207,7 +221,7 @@ class TreeNode implements Node {
      */
     CharSequence getName() {
         return CharSequences.camelCaseToSentence(Classes.getShortName(
-                table.standard.getInterface(metadata.getClass()))).toString();
+                table.standard.getInterface(key()))).toString();
     }
 
     /**
@@ -222,14 +236,6 @@ class TreeNode implements Node {
     }
 
     /**
-     * Returns the base type of values to be returned by {@link #getUserObject()}.
-     * The default implementation is suitable only for the root node - subclasses must override.
-     */
-    public Class<?> getElementType() {
-        return table.standard.getInterface(metadata.getClass());
-    }
-
-    /**
      * The metadata value for this node, to be returned by {@code getValue(TableColumn.VALUE)}.
      * The default implementation is suitable only for the root node - subclasses must override.
      */
@@ -255,6 +261,26 @@ class TreeNode implements Node {
         return false;
     }
 
+    /**
+     * Returns {@code true} if the given object is of the same class than this node and contains a reference
+     * to the same metadata object. Since {@code TreeNode} generates all content from the wrapped metadata,
+     * this condition should ensure that two equal nodes have the same values and children.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        return (other != null) && other.getClass() == getClass()
+                && ((TreeNode) other).metadata == metadata
+                && ((TreeNode) other).baseType == baseType;
+    }
+
+    /**
+     * Returns a hash code value for this node.
+     */
+    @Override
+    public int hashCode() {
+        return System.identityHashCode(metadata) ^ Objects.hashCode(baseType);
+    }
+
 
 
 
@@ -270,15 +296,13 @@ class TreeNode implements Node {
      */
     static class Element extends TreeNode {
         /**
-         * The accessor to use for fetching the property names, types and values from the
-         * {@link #metadata} object. Note that the value of this field is the same for all
-         * siblings.
+         * The accessor to use for fetching the property names, types and values from the {@link #metadata} object.
+         * Note that the reference stored in this field is the same for all siblings.
          */
         private final PropertyAccessor accessor;
 
         /**
-         * Index of the value in the {@link #metadata} object to be fetched with the
-         * {@link #accessor}.
+         * Index of the value in the {@link #metadata} object to be fetched with the {@link #accessor}.
          */
         private final int indexInData;
 
@@ -289,12 +313,12 @@ class TreeNode implements Node {
          * @param  parent       the parent of this node.
          * @param  metadata     the metadata object for which this node will be a value.
          * @param  accessor     accessor to use for fetching the name, type and value.
-         * @param  indexInData  index to be given to the accessor of fetching the value.
+         * @param  indexInData  index to be given to the accessor for fetching the value.
          */
         Element(final TreeNode parent, final Object metadata,
                 final PropertyAccessor accessor, final int indexInData)
         {
-            super(parent, metadata);
+            super(parent, metadata, accessor.type(indexInData, TypeValuePolicy.ELEMENT_TYPE));
             this.accessor = accessor;
             this.indexInData = indexInData;
         }
@@ -328,14 +352,6 @@ class TreeNode implements Node {
         }
 
         /**
-         * Returns the type of property elements.
-         */
-        @Override
-        public final Class<?> getElementType() {
-            return accessor.type(indexInData, TypeValuePolicy.ELEMENT_TYPE);
-        }
-
-        /**
          * Fetches the node value from the metadata object.
          */
         @Override
@@ -358,6 +374,23 @@ class TreeNode implements Node {
         final boolean isWritable() {
             return accessor.isWritable(indexInData);
         }
+
+        /**
+         * Returns {@code true} if the value returned by {@link #getUserObject()}
+         * should be the same for both nodes.
+         */
+        @Override
+        public boolean equals(final Object other) {
+            return super.equals(other) && ((Element) other).indexInData == indexInData;
+        }
+
+        /**
+         * Returns a hash code value for this node.
+         */
+        @Override
+        public int hashCode() {
+            return super.hashCode() ^ (31 * indexInData);
+        }
     }
 
 
@@ -445,7 +478,7 @@ class TreeNode implements Node {
                 }
                 final Iterator<?> it = values.iterator();
                 for (int i=0; i<indexInList; i++) {
-                    it.next(); // Inefficient way to move at the desired index, but hopefully rare.
+                    it.next();      // Inefficient way to move at the desired index, but hopefully rare.
                 }
                 return it.next();
             } catch (NullPointerException | IndexOutOfBoundsException | NoSuchElementException e) {
@@ -475,7 +508,7 @@ class TreeNode implements Node {
                 // in case some implementations have stricter requirements.
                 targetType = ((CheckedContainer<?>) values).getElementType();
             } else {
-                targetType = getElementType();
+                targetType = baseType;
             }
             value = ObjectConverters.convert(value, targetType);
             try {
@@ -492,6 +525,23 @@ class TreeNode implements Node {
                 throw new ConcurrentModificationException(e);
             }
         }
+
+        /**
+         * Returns {@code true} if the value returned by {@link #getUserObject()}
+         * should be the same for both nodes.
+         */
+        @Override
+        public boolean equals(final Object other) {
+            return super.equals(other) && ((CollectionElement) other).indexInList == indexInList;
+        }
+
+        /**
+         * Returns a hash code value for this node.
+         */
+        @Override
+        public int hashCode() {
+            return super.hashCode() ^ indexInList;
+        }
     }
 
 
@@ -536,11 +586,11 @@ class TreeNode implements Node {
                      * to that set, in order to allow this method to check again the next time
                      * that this method is invoked.
                      */
-                    children = null; // Let GC do its work.
+                    children = null;                                    // Let GC do its work.
                     return LEAF;
                 }
             }
-            cachedValue = null; // Use the cached value only once after iteration.
+            cachedValue = null;             // Use the cached value only once after iteration.
             /*
              * If there is a value, check if the cached collection is still applicable.
              */
@@ -554,7 +604,8 @@ class TreeNode implements Node {
              * At this point, we need to create a new collection. The property accessor shall
              * exist, otherwise the call to 'isLeaf()' above would have returned 'true'.
              */
-            children = new TreeNodeChildren(this, value, table.standard.getAccessor(value.getClass(), true));
+            children = new TreeNodeChildren(this, value,
+                    table.standard.getAccessor(new CacheKey(value.getClass(), baseType), true));
         }
         return children;
     }
@@ -715,7 +766,7 @@ class TreeNode implements Node {
         } else if (column == TableColumn.INDEX) {
             value = getIndex();
         } else if (column == TableColumn.TYPE) {
-            value = getElementType();
+            value = baseType;
         }
         return column.getElementType().cast(value);
     }
@@ -777,6 +828,6 @@ class TreeNode implements Node {
      */
     final void toString(final StringBuilder buffer) {
         appendIdentifier(buffer.append("Node["));
-        buffer.append(" : ").append(Classes.getShortName(getElementType())).append(']');
+        buffer.append(" : ").append(Classes.getShortName(baseType)).append(']');
     }
 }



Mime
View raw message