sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1778656 [1/2] - in /sis/branches/JDK8: 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-metada...
Date Fri, 13 Jan 2017 18:58:26 GMT
Author: desruisseaux
Date: Fri Jan 13 18:58:25 2017
New Revision: 1778656

URL: http://svn.apache.org/viewvc?rev=1778656&view=rev
Log:
MetadataStandard.asValueMap(…) / asTreeTable(…) should work even if the argument implements more than one metadata interface (SIS-346).

Added:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java   (with props)
Modified:
    sis/branches/JDK8/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyInformationTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeChildrenTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/ValueMapTest.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java

Modified: sis/branches/JDK8/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java [UTF-8] (original)
+++ sis/branches/JDK8/application/sis-console/src/main/java/org/apache/sis/console/MetadataCommand.java [UTF-8] Fri Jan 13 18:58:25 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/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -177,7 +177,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/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] Fri Jan 13 18:58:25 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);
     }
 
     /**

Added: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java?rev=1778656&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java (added)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.metadata;
+
+import org.apache.sis.util.Debug;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * A key in the {@link MetadataStandard} internal cache.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+final class CacheKey {
+    /**
+     * The metadata class (interface or implementation) for which an {@link PropertyAccessor} will be associated.
+     */
+    final Class<?> type;
+
+    /**
+     * If the {@link #type} is the return value of a property, then the type of that property.
+     * This information allows to handle classes that implement more than one metadata interfaces
+     * for convenience, as for example in the {@link org.apache.sis.internal.simple} package.
+     *
+     * <p>This field shall never be null. If there is no property type information,
+     * then this field shall be set to {@code Object.class}.</p>
+     */
+    final Class<?> propertyType;
+
+    /**
+     * Creates a new key without information on the property type.
+     */
+    CacheKey(final Class<?> type) {
+        this.type = type;
+        propertyType = Object.class;
+    }
+
+    /**
+     * Creates a new key to use in the cache.
+     */
+    CacheKey(final Class<?> type, final Class<?> propertyType) {
+        this.type = type;
+        this.propertyType = (propertyType != null) ? propertyType : Object.class;
+    }
+
+    /**
+     * Returns {@code true} if the {@link #type} can possibly be a value of a property of type
+     * {@link #propertyType}.
+     */
+    final boolean isValid() {
+        return (type != null) && propertyType.isAssignableFrom(type);
+    }
+
+    /**
+     * Returns a hash code value for this key.
+     */
+    @Override
+    public int hashCode() {
+        int code = propertyType.hashCode();
+        if (type != null) code += 31 * type.hashCode();
+        return code;
+    }
+
+    /**
+     * Compares this key with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof CacheKey) {
+            final CacheKey other = (CacheKey) obj;
+            return (type == other.type) && (propertyType == other.propertyType);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a string representation for debugging purpose only.
+     */
+    @Debug
+    @Override
+    public String toString() {
+        String name = (type != null) ? type.getCanonicalName() : "null";
+        if (propertyType != Object.class) {
+            name = name + " as " + propertyType.getSimpleName();
+        }
+        return name;
+    }
+
+    /**
+     * Creates an error message for an unrecognized type.
+     */
+    final String unrecognized() {
+        return Errors.format(Errors.Keys.UnknownType_1, type);
+    }
+
+    /**
+     * Creates an error message for an invalid key.
+     */
+    final String invalid() {
+        return Errors.format(Errors.Keys.IllegalArgumentClass_3, "type", propertyType, type);
+    }
+}

Propchange: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/CacheKey.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/KeyNamePolicy.java [UTF-8] Fri Jan 13 18:58:25 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/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -33,7 +33,6 @@ import org.opengis.metadata.ExtendedElem
 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;
@@ -91,7 +90,7 @@ import static org.apache.sis.util.Argume
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.5
+ * @version 0.8
  * @module
  *
  * @see AbstractMetadata
@@ -186,7 +185,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.
@@ -310,12 +309,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;
         }
@@ -326,44 +325,46 @@ 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) accessors.compute(implementation, (k, v) -> {
+        return (PropertyAccessor) accessors.compute(key, (k, 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;
         });
@@ -379,34 +380,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.
@@ -422,59 +434,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;
@@ -484,14 +501,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);
         }
     }
 
@@ -513,34 +538,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;
     }
 
@@ -597,7 +637,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);
     }
 
     /**
@@ -635,7 +675,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);
     }
 
     /**
@@ -691,7 +731,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);
     }
 
     /**
@@ -727,21 +767,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);
     }
 
     /**
@@ -799,19 +868,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);
     }
 
     /**
@@ -824,7 +918,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);
     }
 
     /**
@@ -864,8 +958,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.
@@ -925,7 +1021,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/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java [UTF-8] Fri Jan 13 18:58:25 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 ControlledVocabulary)) {
                             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/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -59,7 +59,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 +110,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 +154,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 +170,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 +197,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 +220,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 +235,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.
      */
@@ -294,7 +299,7 @@ class TreeNode implements Node {
         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 +333,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
@@ -475,7 +472,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 {
@@ -536,11 +533,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 +551,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 +713,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 +775,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(']');
     }
 }

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -47,7 +47,7 @@ import org.apache.sis.util.Debug;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
@@ -106,9 +106,9 @@ final class TreeNodeChildren extends Abs
     /**
      * Creates a collection of children for the specified metadata.
      *
-     * @param parent   The parent for which this node is an element.
-     * @param metadata The metadata object for which property values will be the elements of this collection.
-     * @param accessor The accessor to use for accessing the property names, types and values of the metadata object.
+     * @param  parent    the parent for which this node is an element.
+     * @param  metadata  the metadata object for which property values will be the elements of this collection.
+     * @param  accessor  the accessor to use for accessing the property names, types and values of the metadata object.
      */
     TreeNodeChildren(final TreeNode parent, final Object metadata, final PropertyAccessor accessor) {
         this.parent   = parent;
@@ -132,7 +132,7 @@ final class TreeNodeChildren extends Abs
      * Passing null avoid the type check and is safe at least with SIS implementation. We may revisit
      * later if this appears to be a problem with other implementations.
      *
-     * @param index The index in the accessor (<em>not</em> the index in this collection).
+     * @param  index  the index in the accessor (<em>not</em> the index in this collection).
      */
     final void clearAt(final int index) {
         accessor.set(index, metadata, null, PropertyAccessor.RETURN_NULL);
@@ -142,8 +142,8 @@ final class TreeNodeChildren extends Abs
      * Returns the value at the given index. The given {@code index} is relative to
      * the {@link #accessor} indexing, <strong>not</strong> to this collection.
      *
-     * @param  index The index in the accessor (<em>not</em> the index in this collection).
-     * @return The value at the given index. May be {@code null} or a collection.
+     * @param  index  the index in the accessor (<em>not</em> the index in this collection).
+     * @return the value at the given index. May be {@code null} or a collection.
      */
     final Object valueAt(final int index) {
         return accessor.get(index, metadata);
@@ -158,7 +158,7 @@ final class TreeNodeChildren extends Abs
      * We do not test {@code (value instanceof Collection)} because the value could be any user's implementation.
      * Nothing prevent users from implementing the collection interface even for singleton elements if they wish.</div>
      *
-     * @param  index The index in the accessor (<em>not</em> the index in this collection).
+     * @param  index  the index in the accessor (<em>not</em> the index in this collection).
      * @return {@code true} if the value at the given index is a collection.
      */
     final boolean isCollection(final int index) {
@@ -169,7 +169,7 @@ final class TreeNodeChildren extends Abs
      * Returns {@code true} if the give value shall be skipped by the iterators,
      * according the value policy.
      *
-     * @param  value The value to test.
+     * @param  value  the value to test.
      * @return {@code true} if the given value shall be skipped by the iterators.
      */
     final boolean isSkipped(final Object value) {
@@ -183,10 +183,10 @@ final class TreeNodeChildren extends Abs
      * <p>This method does not check if the child at the given index should be skipped.
      * It is caller responsibility to do such verification before this method call.</p>
      *
-     * @param  index The index in the accessor (<em>not</em> the index in this collection).
-     * @param  subIndex If the property at {@link #index} is a collection, the index in that
+     * @param  index     the index in the accessor (<em>not</em> the index in this collection).
+     * @param  subIndex  if the property at {@link #index} is a collection, the index in that
      *         collection (<em>not</em> the index in <em>this</em> collection). Otherwise -1.
-     * @return The node to be returned by public API.
+     * @return the node to be returned by public API.
      */
     final TreeNode childAt(final int index, final int subIndex) {
         TreeNode node = children[index];
@@ -198,7 +198,6 @@ final class TreeNodeChildren extends Abs
              */
             if (node == null || ((TreeNode.CollectionElement) node).indexInList != subIndex) {
                 node = new TreeNode.CollectionElement(parent, metadata, accessor, index, subIndex);
-                node.init();
             }
         } else {
             /*
@@ -208,7 +207,6 @@ final class TreeNodeChildren extends Abs
              */
             if (node == null) {
                 node = new TreeNode.Element(parent, metadata, accessor, index);
-                node.init();
             }
         }
         children[index] = node;
@@ -481,7 +479,7 @@ final class TreeNodeChildren extends Abs
      * <p>This method does not iterate explicitly through the children list, because adding a metadata
      * object implicitly adds all its children.</p>
      *
-     * @param  node The node from which to get the values.
+     * @param  node  the node from which to get the values.
      * @return {@code true} if the metadata changed as a result of this method call.
      * @throws NullPointerException if the given node is null.
      * @throws IllegalArgumentException if this list does not have a property for the node identifier.
@@ -504,8 +502,8 @@ final class TreeNodeChildren extends Abs
      * Implementation of {@link #add(TreeTable.Node)}, also invoked by {@link TreeNode.NewChild}.
      * This method will attempt to convert the given {@code value} to the expected type.
      *
-     * @param  index The index in the accessor (<em>not</em> the index in this collection).
-     * @param  value The property value to add.
+     * @param  index  the index in the accessor (<em>not</em> the index in this collection).
+     * @param  value  the property value to add.
      * @return {@code true} if the metadata changed as a result of this method call.
      */
     final boolean add(final int index, final Object value) throws IllegalStateException {

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -46,14 +46,14 @@ import org.apache.sis.internal.system.Se
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.5
+ * @version 0.8
  * @module
  */
 final class TreeTableView implements TreeTable, Serializable {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 6320615192545089879L;
+    private static final long serialVersionUID = 3911016927808764394L;
 
     /**
      * The columns to be returned by {@link #getColumns()}.
@@ -94,14 +94,17 @@ final class TreeTableView implements Tre
     /**
      * Creates a tree table for the specified metadata object.
      *
-     * @param standard    The metadata standard implemented by the given metadata.
-     * @param metadata    The metadata object to wrap.
-     * @param valuePolicy The behavior of this map toward null or empty values.
-     */
-    TreeTableView(final MetadataStandard standard, final Object metadata, final ValueExistencePolicy valuePolicy) {
+     * @param  standard     the metadata standard implemented by the given metadata.
+     * @param  metadata     the metadata object to wrap.
+     * @param  baseType     base type of {@code metadata} interfaces to take in account.
+     * @param  valuePolicy  the behavior of this map toward null or empty values.
+     */
+    TreeTableView(final MetadataStandard standard, final Object metadata,
+            final Class<?> baseType, final ValueExistencePolicy valuePolicy)
+    {
         this.standard    = standard;
         this.valuePolicy = valuePolicy;
-        this.root = new TreeNode(this, metadata);
+        this.root = new TreeNode(this, metadata, baseType);
     }
 
     /**
@@ -128,7 +131,7 @@ final class TreeTableView implements Tre
      * developers are encouraged to create and configure their own {@link TreeTableFormat}
      * instance.
      *
-     * @return A string representation of this tree table.
+     * @return a string representation of this tree table.
      */
     @Override
     public String toString() {
@@ -160,23 +163,25 @@ final class TreeTableView implements Tre
     /**
      * Invoked on serialization. Write the metadata object instead of the {@linkplain #root} node.
      *
-     * @param  out The output stream where to serialize this object.
-     * @throws IOException If an I/O error occurred while writing.
+     * @param  out  the output stream where to serialize this object.
+     * @throws IOException if an I/O error occurred while writing.
      */
     private void writeObject(final ObjectOutputStream out) throws IOException {
         out.defaultWriteObject();
+        out.writeObject(root.baseType);
         out.writeObject(root.metadata);
     }
 
     /**
      * Invoked on deserialization. Recreate the {@linkplain #root} node from the metadata object.
      *
-     * @param  in The input stream from which to deserialize an object.
-     * @throws IOException If an I/O error occurred while reading or if the stream contains invalid data.
-     * @throws ClassNotFoundException If the class serialized on the stream is not on the classpath.
+     * @param  in  the input stream from which to deserialize an object.
+     * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
+     * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
      */
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
-        root = new TreeNode(this, in.readObject());
+        final Class<?> baseType = (Class<?>) in.readObject();
+        root = new TreeNode(this, in.readObject(), baseType);
     }
 }

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -42,7 +42,7 @@ import org.apache.sis.xml.NilReason;
  * @version 0.4
  * @module
  *
- * @see MetadataStandard#asValueMap(Object, KeyNamePolicy, ValueExistencePolicy)
+ * @see MetadataStandard#asValueMap(Object, Class, KeyNamePolicy, ValueExistencePolicy)
  */
 public enum ValueExistencePolicy {
     /**

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -35,7 +35,7 @@ import static org.apache.sis.metadata.Pr
  * @version 0.3
  * @module
  *
- * @see MetadataStandard#asValueMap(Object, KeyNamePolicy, ValueExistencePolicy)
+ * @see MetadataStandard#asValueMap(Object, Class, KeyNamePolicy, ValueExistencePolicy)
  */
 final class ValueMap extends PropertyMap<Object> {
     /**

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -120,7 +120,7 @@
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Adrian Custer (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 package org.apache.sis.metadata;

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java?rev=1778656&r1=1778655&r2=1778656&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java [UTF-8] Fri Jan 13 18:58:25 2017
@@ -541,7 +541,7 @@ public class MetadataSource implements A
      *         of the expected package.
      */
     private Map<String,Object> asMap(final Object metadata) throws ClassCastException {
-        return standard.asValueMap(metadata, KeyNamePolicy.UML_IDENTIFIER, ValueExistencePolicy.ALL);
+        return standard.asValueMap(metadata, null, KeyNamePolicy.UML_IDENTIFIER, ValueExistencePolicy.ALL);
     }
 
     /**



Mime
View raw message