You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2017/01/15 10:36:24 UTC
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...
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(\u2026).
- 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(\u2026)} 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(\u2026).type) would have been enough, but checking
* for isAssignableFrom(\u2026) first can avoid the (relatively costly) creation of new PropertyAccessor.
@@ -933,7 +1029,7 @@ public class MetadataStandard implements
// See comment in 'equals(\u2026) 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(\u2026)} 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(']');
}
}