This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit b5bad671d8a45577655c1b4378f1096f503119e2 Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri May 22 16:28:55 2026 +0200 Remove the `SecondaryTrait` internal annotation and use the information provided by the `getStandardType()` method instead. --- .../org/apache/sis/metadata/AbstractMetadata.java | 17 +- .../main/org/apache/sis/metadata/CacheKey.java | 29 ++- .../org/apache/sis/metadata/MetadataStandard.java | 279 ++++++++++++--------- .../org/apache/sis/metadata/MetadataVisitor.java | 14 +- .../apache/sis/metadata/ModifiableMetadata.java | 3 +- .../main/org/apache/sis/metadata/Pruner.java | 2 +- .../sis/metadata/StandardImplementation.java | 6 +- .../main/org/apache/sis/metadata/StateChanger.java | 2 +- .../main/org/apache/sis/metadata/TreeNode.java | 10 +- .../org/apache/sis/metadata/TreeNodeChildren.java | 4 +- .../sis/metadata/internal/shared/Merger.java | 10 +- .../metadata/internal/shared/SecondaryTrait.java | 45 ---- .../apache/sis/metadata/simple/SimpleMetadata.java | 31 ++- .../apache/sis/metadata/MetadataStandardTest.java | 8 +- .../apache/sis/metadata/TreeNodeChildrenTest.java | 2 +- .../sis/referencing/AbstractIdentifiedObject.java | 24 +- .../main/org/apache/sis/referencing/Builder.java | 6 +- .../referencing/datum/DefaultDatumEnsemble.java | 2 - .../datum/DefaultDatumEnsembleTest.java | 47 +++- .../main/org/apache/sis/storage/gpx/Metadata.java | 6 +- .../main/org/apache/sis/storage/gpx/Store.java | 2 +- .../main/org/apache/sis/util/Classes.java | 44 ++-- .../org/apache/sis/util/LenientComparable.java | 3 +- 23 files changed, 332 insertions(+), 264 deletions(-) diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java index 83a8444a18..44b6af2dce 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/AbstractMetadata.java @@ -160,14 +160,9 @@ public abstract class AbstractMetadata implements LenientComparable, Emptiable { * @return the standard interface implemented by this class. * @deprecated Renamed {@link #getStandardType()} for integration with {@link LenientComparable}. */ - // TODO: make package private with modification: return null instead of ClassCastException. @Deprecated(since="1.7", forRemoval=true) public final Class<?> getInterface() { - Type type = getStandardType(); - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getRawType(); - } - return (Class<?>) type; + return org.apache.sis.util.Classes.getRawClass(getStandardType()); } /** @@ -353,14 +348,16 @@ public abstract class AbstractMetadata implements LenientComparable, Emptiable { } /** - * Compares this metadata with the specified object for equality. The default - * implementation uses Java reflection. Subclasses may override this method - * for better performances, or for comparing "hidden" properties not specified - * by the GeoAPI (or other standard) interface. + * Compares this metadata with the specified object for equality. + * The default implementation uses Java reflection using {@link MetadataStandard} methods. + * Subclasses may override this method for comparing properties that are not specified by + * the GeoAPI (or other standard) interface. * * @param object the object to compare with this metadata. * @param mode the strictness level of the comparison. * @return {@code true} if the given object is equal to this metadata. + * + * @see MetadataStandard#equals(Object, Object, ComparisonMode) */ @Override public boolean equals(final Object object, final ComparisonMode mode) { diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/CacheKey.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/CacheKey.java index 9dc8c4ac42..87b3c705e7 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/CacheKey.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/CacheKey.java @@ -33,34 +33,33 @@ final class CacheKey { final Class<?> type; /** - * If the {@link #type} is an implementation class of a property, then the type declared in the signature for - * that property. This information allows to handle classes that implement more than one metadata interfaces - * for their convenience. Some examples are found in the {@link org.apache.sis.metadata.simple} package. + * If the {@link #type} is the type of the value of a property, the type declared in the property signature. + * This information allows to resolve ambiguities when a class implements more than one metadata interface. + * Some examples are found in the {@link org.apache.sis.metadata.simple} package. + * + * <p><b>Invariant:</b> {@code propertyType.isAssignableFrom(type)}</p> * * <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. + * The {@code propertyType.isInstance(metadata)} condition should always be {@code true}, + * but this is not verified by this constructor. Instead, the validity can be verified + * after constructor with {@link #isValid()}. + * + * @param type the metadata class, or {@code null}. + * @param propertyType base class of the metadata object. */ CacheKey(final Class<?> type, final Class<?> propertyType) { this.type = type; - this.propertyType = (propertyType != null) ? propertyType : Object.class; + this.propertyType = propertyType; } /** - * Returns {@code true} if the {@link #type} can possibly be a value of a property of type - * {@link #propertyType}. + * Returns whether instances of {@link #type} can can be values of a property of type {@link #propertyType}. */ final boolean isValid() { return (type != null) && propertyType.isAssignableFrom(type); @@ -82,7 +81,7 @@ final class CacheKey { @Override public boolean equals(final Object obj) { if (obj instanceof CacheKey) { - final CacheKey other = (CacheKey) obj; + final var other = (CacheKey) obj; return (type == other.type) && (propertyType == other.propertyType); } return false; diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java index cbda84ff8d..f485016d8f 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataStandard.java @@ -18,6 +18,7 @@ package org.apache.sis.metadata; import java.util.Set; import java.util.Map; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Iterator; import java.util.Objects; @@ -29,9 +30,10 @@ import java.io.ObjectInputStream; import org.opengis.metadata.Identifier; import org.opengis.metadata.ExtendedElementInformation; import org.opengis.metadata.citation.Citation; -import org.apache.sis.metadata.internal.shared.SecondaryTrait; import org.apache.sis.util.Classes; import org.apache.sis.util.ComparisonMode; +import org.apache.sis.util.LenientComparable; +import org.apache.sis.util.OptionalCandidate; import org.apache.sis.util.collection.TreeTable; import org.apache.sis.util.collection.TableColumn; import org.apache.sis.util.collection.CheckedContainer; @@ -91,7 +93,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement; * by a large number of {@link ModifiableMetadata} instances. * * @author Martin Desruisseaux (Geomatys) - * @version 1.5 + * @version 1.7 * * @see AbstractMetadata * @@ -101,7 +103,12 @@ public class MetadataStandard implements Serializable { /** * For cross-version compatibility. */ - private static final long serialVersionUID = 7549790450195184843L; + private static final long serialVersionUID = -162351202766308250L; + + /** + * Constant array for a standard with no dependency. + */ + private static final MetadataStandard[] NO_DEPENDENCY = {}; /** * {@code true} if implementations can alter the API defined in the interfaces by @@ -154,7 +161,7 @@ public class MetadataStandard implements Serializable { static { final String[] acronyms = {"CoordinateSystem", "CS", "CoordinateReferenceSystem", "CRS"}; - ISO_19115 = new StandardImplementation("ISO 19115", "org.opengis.metadata.", "org.apache.sis.metadata.iso.", null, (MetadataStandard[]) null); + ISO_19115 = new StandardImplementation("ISO 19115", "org.opengis.metadata.", "org.apache.sis.metadata.iso.", null, NO_DEPENDENCY); ISO_19157 = new StandardImplementation("ISO 19157", "org.opengis.metadata.quality.", "org.apache.sis.metadata.iso.quality.", null, ISO_19115); ISO_19111 = new StandardImplementation("ISO 19111", "org.opengis.referencing.", "org.apache.sis.referencing.", acronyms, ISO_19157, ISO_19115); ISO_19123 = new MetadataStandard ("ISO 19123", "org.opengis.coverage.", ISO_19111); @@ -185,12 +192,10 @@ public class MetadataStandard implements Serializable { final String interfacePackage; /** - * The dependencies, or {@code null} if none. - * If non-null, dependencies will be tested in the order they appear in this array. + * The dependencies, or an empty array if none. + * Dependencies will be tested in the order they appear in this array. * Consequently, if {@link #isMetadata(Class)} may return {@code true} for two or more * dependencies, then the dependency which should have precedence should be declared first. - * - * <p>Note: the {@code null} value is for serialization compatibility.</p> */ private final MetadataStandard[] dependencies; @@ -207,7 +212,7 @@ public class MetadataStandard implements Serializable { * Consider this field as final. * It is not final only for {@link #readObject(ObjectInputStream)} purpose. */ - private transient ConcurrentMap<CacheKey,Object> accessors; + private transient ConcurrentMap<CacheKey, Object> accessors; /** * Creates a new instance working on implementation of interfaces defined in the specified package. @@ -233,13 +238,14 @@ public class MetadataStandard implements Serializable { this.interfacePackage = interfacePackage.getName() + '.'; this.accessors = new ConcurrentHashMap<>(); // Also defined in readObject(…) if (dependencies.length == 0) { - this.dependencies = null; + dependencies = NO_DEPENDENCY; } else { - this.dependencies = dependencies = dependencies.clone(); + dependencies = dependencies.clone(); for (int i=0; i<dependencies.length; i++) { ensureNonNullElement("dependencies", i, dependencies[i]); } } + this.dependencies = dependencies; } /** @@ -248,7 +254,7 @@ public class MetadataStandard implements Serializable { * * @param citation bibliographical reference to the international standard. * @param interfacePackage the root package for metadata interfaces. - * @param dependencies the dependencies to other metadata standards, or {@code null} if none. + * @param dependencies the dependencies to other metadata standards. */ MetadataStandard(final String citation, final String interfacePackage, final MetadataStandard... dependencies) { this.citation = new SimpleCitation(citation); @@ -320,15 +326,65 @@ public class MetadataStandard implements Serializable { } /** - * Returns a key for use in {@link #getAccessor(CacheKey, boolean)} for the given type. + * Creates a new set initialized with {@code this} if the given set is null. + * + * @param visited metadata standards already visited, or {@code null} if none. + * @return non-null set where to record that a metadata standard have been visited. + */ + private Set<MetadataStandard> createIfNull(Set<MetadataStandard> visited) { + if (visited == null) { + visited = new HashSet<>(); + visited.add(this); + } + return visited; + } + + /** + * Returns the accessor for the specified interface or implementation class. * The type may be an interface (typically a GeoAPI interface) or an implementation class. + * + * @param key the interface or implementation class. + * @param mandatory whether this method shall throw an exception if no accessor is found. + * @return the accessor for the given type, or {@code null} if none and {@code mandatory} is {@code false}. */ - private CacheKey createCacheKey(Class<?> type) { + final PropertyAccessor getTypeAccessor(Class<?> type, final boolean mandatory) { + Class<?> propertyType = Object.class; final Class<?> implementation = getImplementation(type); if (implementation != null) { + propertyType = type; type = implementation; } - return new CacheKey(type); + return getAccessor(new CacheKey(type, propertyType), null, mandatory); + } + + /** + * Returns the accessor for the specified metadata instance. + * The {@code propertyType.isInstance(metadata)} condition should always be {@code true}, + * but this is not verified by this constructor. Instead, the validity can be verified + * after constructor with {@link CacheKey#isValid()}. + * + * <p>A null value for {@code propertyType} is not equivalent to {@code Object.class}. + * If the value is null, this constructor tries to detect the interface automatically. + * If the value is {@code Object.class}, no detection is attempted, which is faster. + * In particular, if a {@code null} value would cause the default implementation of + * {@link AbstractMetadata#getStandardType()} to be invoked, then it is preferable + * to specify {@code Object.class} for avoiding to do reflection twice.</p> + * + * @param metadata the metadata object. + * @param propertyType base class of the metadata object, or {@code null} if unknown. + * @param mandatory whether this method shall throw an exception if no accessor is found. + * @return the accessor for the given object, or {@code null} if none and {@code mandatory} is {@code false}. + */ + final PropertyAccessor getInstanceAccessor(final Object metadata, Class<?> propertyType, final boolean mandatory) { + if (propertyType == null) { + if (metadata instanceof LenientComparable) { + propertyType = Classes.getRawClass(((LenientComparable) metadata).getStandardType()); + } + if (propertyType == null) { + propertyType = Object.class; + } + } + return getAccessor(new CacheKey(metadata.getClass(), propertyType), null, mandatory); } /** @@ -342,14 +398,15 @@ public class MetadataStandard implements Serializable { * </ul> * * @param key the implementation class together with the type declared by the property. + * @param visited metadata standards already visited, or {@code null} if none. * @param mandatory whether this method shall throw an exception or return {@code null} - * if no accessor is found for the given implementation class. + * if no accessor is found for the given implementation class. * @return the accessor for the given implementation, or {@code null} if the given class does not * implement a metadata interface of the expected package and {@code mandatory} is {@code false}. - * @throws ClassCastException if the specified class does not implement a metadata interface - * of the expected package and {@code mandatory} is {@code true}. + * @throws ClassCastException if the specified class does not implement an expected metadata interface + * and {@code mandatory} is {@code true}. */ - final PropertyAccessor getAccessor(final CacheKey key, final boolean mandatory) { + private PropertyAccessor getAccessor(final CacheKey key, Set<MetadataStandard> visited, final boolean mandatory) { /* * Check for accessors created by previous calls to this method. * Values are added to this cache but never cleared. @@ -375,9 +432,10 @@ public class MetadataStandard implements Serializable { */ type = findInterface(key); if (type == null) { - if (dependencies != null) { - for (final MetadataStandard dependency : dependencies) { - final PropertyAccessor accessor = dependency.getAccessor(key, false); + visited = createIfNull(visited); + for (final MetadataStandard dependency : dependencies) { + if (visited.add(dependency)) { + final PropertyAccessor accessor = dependency.getAccessor(key, visited, false); if (accessor != null) { accessors.put(key, accessor); // Ok to overwrite existing instance here. return accessor; @@ -421,28 +479,26 @@ public class MetadataStandard implements Serializable { * or implements an interface of this standard. */ public boolean isMetadata(final Class<?> type) { - return (type != null) && !type.isPrimitive() && isMetadata(new CacheKey(type)); + return (type != null) && !type.isPrimitive() && isMetadata(new CacheKey(type, Object.class)); } /** * 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. + * We do not provide the additional functionality of this method in public <abbr>API</abbr> 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) + * @see #getInterface(CacheKey, Set) */ 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; - } + for (final MetadataStandard dependency : dependencies) { + if (dependency.isMetadata(key)) { + accessors.putIfAbsent(key, dependency); + return true; } } /* @@ -488,9 +544,10 @@ public class MetadataStandard implements Serializable { */ private Class<?> findInterface(final CacheKey key) { assert key.isValid() : key; - if (key.type.isInterface()) { - if (isSupported(key.type.getName())) { - return key.type; + final Class<?> type = key.type; + if (type.isInterface()) { + if (isSupported(type.getName())) { + return type; } } else { /* @@ -499,38 +556,35 @@ public class MetadataStandard implements Serializable { * tells whether the type is supported. Types associated to `FALSE` * shall be ignored. */ - final var validities = new LinkedHashMap<Class<?>, Boolean>(); - final SecondaryTrait ignore = key.type.getAnnotation(SecondaryTrait.class); - if (ignore != null) { - validities.put(ignore.value(), Boolean.FALSE); - } - for (Class<?> t=key.type; t!=null; t=t.getSuperclass()) { - getInterfaces(t, key.propertyType, validities); + final var interfaces = new LinkedHashMap<Class<?>, Boolean>(); + for (Class<?> t = type; t != null; t = t.getSuperclass()) { + getInterfaces(t, key.propertyType, interfaces); } /* * Remove all unsupported types. Then, if we found more than one supported * interface, remove the ones that are sub-interfaces of the other. */ - validities.values().removeIf((isSupported) -> !isSupported); - final Set<Class<?>> interfaces = validities.keySet(); - for (final Iterator<Class<?>> it=interfaces.iterator(); it.hasNext();) { + interfaces.values().removeIf((isSupported) -> !isSupported); + final Set<Class<?>> validInterfaces = interfaces.keySet(); + Iterator<Class<?>> it = validInterfaces.iterator(); + while (it.hasNext()) { final Class<?> candidate = it.next(); - for (final Class<?> other : interfaces) { + for (final Class<?> other : validInterfaces) { if (candidate != other && candidate.isAssignableFrom(other)) { it.remove(); break; } } } - final Iterator<Class<?>> it = interfaces.iterator(); + it = validInterfaces.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 throw an exception. + * Found more than one interface and we don't know which one to pick. + * Returns `null` for now, the caller will throw an exception. */ } else if (IMPLEMENTATION_CAN_ALTER_API) { /* @@ -542,8 +596,8 @@ public class MetadataStandard implements Serializable { * 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. */ - if (isPendingAPI(key.type)) { - return key.type; + if (isPendingAPI(type)) { + return type; } } } @@ -553,7 +607,7 @@ public class MetadataStandard implements Serializable { /** * Puts every interfaces for the given type in the specified map. * This method invokes itself recursively for scanning parent interfaces. - * The keys tell whether the interface is supported. validities + * The keys tell whether the interface is supported. * * <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 @@ -563,7 +617,7 @@ public class MetadataStandard implements Serializable { * * @see Classes#getAllInterfaces(Class) */ - private void getInterfaces(final Class<?> type, final Class<?> propertyType, final Map<Class<?>, Boolean> validities) { + private void getInterfaces(final Class<?> type, final Class<?> propertyType, final Map<Class<?>, Boolean> addTo) { for (final Class<?> candidate : type.getInterfaces()) { final boolean recursive = propertyType.isAssignableFrom(candidate); if (recursive || (IMPLEMENTATION_CAN_ALTER_API && isPendingAPI(propertyType))) { @@ -574,8 +628,8 @@ public class MetadataStandard implements Serializable { * we skip the `isAssignableFrom` check, but without recursive addition of parent interfaces since we * would not know when to stop. */ - if (validities.putIfAbsent(candidate, isSupported(candidate.getName())) == null && recursive) { - getInterfaces(candidate, propertyType, validities); + if (addTo.putIfAbsent(candidate, isSupported(candidate.getName())) == null && recursive) { + getInterfaces(candidate, propertyType, addTo); } } } @@ -599,8 +653,8 @@ public class MetadataStandard implements Serializable { * * @see AbstractMetadata#getStandardType() */ - public <T> Class<? super T> getInterface(final Class<T> type) throws ClassCastException { - return getInterface(new CacheKey(Objects.requireNonNull(type))); + public <T> Class<? super T> getInterface(final Class<T> type) { + return getInterface(new CacheKey(Objects.requireNonNull(type), Object.class), null); } /** @@ -610,10 +664,14 @@ public class MetadataStandard implements Serializable { * 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. * + * @param key the implementation class together with the type declared by the property. + * @param visited metadata standards already visited, or {@code null} if none. + * @throws ClassCastException if the implementation class does not implement an interface of this standard. + * * @see #isMetadata(CacheKey) */ @SuppressWarnings("unchecked") - final <T> Class<? super T> getInterface(final CacheKey key) throws ClassCastException { + final <T> Class<? super T> getInterface(final CacheKey key, Set<MetadataStandard> visited) { final Class<?> interf; final Object value = accessors.get(key); if (value instanceof PropertyAccessor) { @@ -621,18 +679,17 @@ public class MetadataStandard implements Serializable { } else if (value instanceof Class<?>) { interf = (Class<?>) value; } else if (value instanceof MetadataStandard) { - interf = ((MetadataStandard) value).getInterface(key); + interf = ((MetadataStandard) value).getInterface(key, createIfNull(visited)); } else if (key.isValid()) { interf = findInterface(key); if (interf != null) { accessors.putIfAbsent(key, interf); } else { - if (dependencies != null) { - for (final MetadataStandard dependency : dependencies) { - if (dependency.isMetadata(key)) { - accessors.putIfAbsent(key, dependency); - return dependency.getInterface(key); - } + visited = createIfNull(visited); + for (final MetadataStandard dependency : dependencies) { + if (dependency.isMetadata(key)) { + accessors.putIfAbsent(key, dependency); + return dependency.getInterface(key, visited); } } throw new ClassCastException(key.unrecognized()); @@ -657,6 +714,7 @@ public class MetadataStandard implements Serializable { * @param type the interface, typically from the {@code org.opengis.metadata} package. * @return the implementation class, or {@code null} if none. */ + @OptionalCandidate public <T> Class<? extends T> getImplementation(final Class<T> type) { return null; } @@ -674,7 +732,7 @@ public class MetadataStandard implements Serializable { final Object getTitle(final Object metadata) { if (metadata != null) { final Class<?> type = metadata.getClass(); - final PropertyAccessor accessor = getAccessor(createCacheKey(type), false); + final PropertyAccessor accessor = getTypeAccessor(type, false); if (accessor != null) { TitleProperty an = type.getAnnotation(TitleProperty.class); if (an != null || (an = accessor.implementation.getAnnotation(TitleProperty.class)) != null) { @@ -714,13 +772,11 @@ public class MetadataStandard implements Serializable { * @throws ClassCastException if the specified interface or implementation class does * not extend or implement a metadata interface of the expected package. */ - public Map<String,String> asNameMap(final Class<?> type, final KeyNamePolicy keyPolicy, - final KeyNamePolicy valuePolicy) throws ClassCastException - { + public Map<String, String> asNameMap(Class<?> type, KeyNamePolicy keyPolicy, KeyNamePolicy valuePolicy) { ensureNonNull("type", type); ensureNonNull("keyPolicy", keyPolicy); ensureNonNull("valuePolicy", valuePolicy); - return new NameMap(getAccessor(createCacheKey(type), true), keyPolicy, valuePolicy); + return new NameMap(getTypeAccessor(type, true), keyPolicy, valuePolicy); } /** @@ -735,7 +791,7 @@ public class MetadataStandard implements Serializable { * * {@snippet lang="java" : * MetadataStandard standard = MetadataStandard.ISO_19115; - * Map<String,Class<?>> types = standard.asTypeMap(Citation.class, UML_IDENTIFIER, ELEMENT_TYPE); + * Map<String, Class<?>> types = standard.asTypeMap(Citation.class, UML_IDENTIFIER, ELEMENT_TYPE); * Class<?> value = types.get("alternateTitle"); * System.out.println(value); // class org.opengis.util.InternationalString * } @@ -748,13 +804,11 @@ public class MetadataStandard implements Serializable { * @throws ClassCastException if the specified interface or implementation class does * not extend or implement a metadata interface of the expected package. */ - public Map<String,Class<?>> asTypeMap(final Class<?> type, final KeyNamePolicy keyPolicy, - final TypeValuePolicy valuePolicy) throws ClassCastException - { + public Map<String, Class<?>> asTypeMap(Class<?> type, KeyNamePolicy keyPolicy, TypeValuePolicy valuePolicy) { ensureNonNull("type", type); ensureNonNull("keyPolicy", keyPolicy); ensureNonNull("valuePolicy", valuePolicy); - return new TypeMap(getAccessor(createCacheKey(type), true), keyPolicy, valuePolicy); + return new TypeMap(getTypeAccessor(type, true), keyPolicy, valuePolicy); } /** @@ -801,12 +855,10 @@ public class MetadataStandard implements Serializable { * * @see org.apache.sis.metadata.iso.DefaultExtendedElementInformation */ - public Map<String,ExtendedElementInformation> asInformationMap(final Class<?> type, final KeyNamePolicy keyPolicy) - throws ClassCastException - { + public Map<String, ExtendedElementInformation> asInformationMap(Class<?> type, KeyNamePolicy keyPolicy) { ensureNonNull("type", type); ensureNonNull("keyNames", keyPolicy); - return new InformationMap(citation, getAccessor(createCacheKey(type), true), keyPolicy); + return new InformationMap(citation, getTypeAccessor(type, true), keyPolicy); } /** @@ -826,12 +878,10 @@ public class MetadataStandard implements Serializable { * @throws ClassCastException if the specified interface or implementation class does * not extend or implement a metadata interface of the expected package. */ - public Map<String,Integer> asIndexMap(final Class<?> type, final KeyNamePolicy keyPolicy) - throws ClassCastException - { + public Map<String, Integer> asIndexMap(Class<?> type, KeyNamePolicy keyPolicy) { ensureNonNull("type", type); ensureNonNull("keyPolicy", keyPolicy); - return new IndexMap(getAccessor(createCacheKey(type), true), keyPolicy); + return new IndexMap(getTypeAccessor(type, true), keyPolicy); } /** @@ -887,13 +937,15 @@ public class MetadataStandard implements Serializable { * * @since 0.8 */ - public Map<String,Object> asValueMap(final Object metadata, final Class<?> baseType, - 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) { ensureNonNull("metadata", metadata); ensureNonNull("keyPolicy", keyPolicy); ensureNonNull("valuePolicy", valuePolicy); - return new ValueMap(metadata, getAccessor(new CacheKey(metadata.getClass(), baseType), true), keyPolicy, valuePolicy); + return new ValueMap(metadata, getInstanceAccessor(metadata, baseType, true), keyPolicy, valuePolicy); } /** @@ -925,12 +977,10 @@ public class MetadataStandard implements Serializable { * * @since 1.5 */ - public Map<String,NilReason> asNilReasonMap(final Object metadata, final Class<?> baseType, - final KeyNamePolicy keyPolicy) throws ClassCastException - { + public Map<String, NilReason> asNilReasonMap(Object metadata, Class<?> baseType, KeyNamePolicy keyPolicy) { ensureNonNull("metadata", metadata); ensureNonNull("keyPolicy", keyPolicy); - return new NilReasonMap(metadata, getAccessor(new CacheKey(metadata.getClass(), baseType), true), keyPolicy); + return new NilReasonMap(metadata, getInstanceAccessor(metadata, baseType, true), keyPolicy); } /** @@ -1015,9 +1065,7 @@ public class MetadataStandard implements Serializable { * * @since 0.8 */ - public TreeTable asTreeTable(final Object metadata, Class<?> baseType, final ValueExistencePolicy valuePolicy) - throws ClassCastException - { + public TreeTable asTreeTable(final Object metadata, Class<?> baseType, final ValueExistencePolicy valuePolicy) { ensureNonNull("metadata", metadata); ensureNonNull("valuePolicy", valuePolicy); if (baseType == null) { @@ -1027,31 +1075,21 @@ public class MetadataStandard implements Serializable { } /** - * Compares the two specified metadata objects. + * Compares the two specified metadata objects for equality. * The two metadata arguments shall be implementations of a metadata interface defined by * this {@code MetadataStandard}, otherwise an exception will be thrown. However, the two - * arguments do not need to be the same implementation class. - * - * <h4>Shallow or deep comparisons</h4> - * This method implements a <em>shallow</em> comparison in that properties are compared by - * invoking their {@code properties.equals(…)} method without <em>explicit</em> recursive call - * to this {@code standard.equals(…)} method for children metadata. However, the comparison will - * do <em>implicit</em> recursive calls if the {@code properties.equals(…)} implementations - * delegate their work to this {@code standard.equals(…)} method, as {@link AbstractMetadata} does. - * In the latter case, the final result is a deep comparison. - * - * @param metadata1 the first metadata object to compare. - * @param metadata2 the second metadata object to compare. + * arguments do not need to be instances of the same implementation class + * except for {@link ComparisonMode#STRICT}. + * + * @param metadata1 the first metadata object to compare, or {@code null}. + * @param metadata2 the second metadata object to compare, or {@code null}. * @param mode the strictness level of the comparison. - * @return {@code true} if the given metadata objects are equals. - * @throws ClassCastException if at least one metadata object does not - * implement a metadata interface of the expected package. + * @return {@code true} if the given metadata objects are equals or if the two arguments are {@code null}. + * @throws ClassCastException if {@code metadata1} does not implement an expected metadata interface. * * @see AbstractMetadata#equals(Object, ComparisonMode) */ - public boolean equals(final Object metadata1, final Object metadata2, - final ComparisonMode mode) throws ClassCastException - { + public boolean equals(final Object metadata1, final Object metadata2, final ComparisonMode mode) { if (metadata1 == metadata2) { return true; } @@ -1063,14 +1101,15 @@ public class MetadataStandard implements Serializable { if (type1 != type2 && mode == ComparisonMode.STRICT) { return false; } - final PropertyAccessor accessor = getAccessor(new CacheKey(type1), true); + final PropertyAccessor accessor = getInstanceAccessor(metadata1, null, true); if (type1 != type2) { + final var key = new CacheKey(type2, accessor.type); // Not strictly necessary, but can avoid the relatively costly creation of new `PropertyAccessor`. if (!accessor.type.isAssignableFrom(type2)) { return false; } - // The real condition. - if (accessor.type != getAccessor(new CacheKey(type2), false).type) { + final PropertyAccessor other = getAccessor(key, null, false); + if (other == null || accessor.type != other.type) { return false; } } @@ -1079,7 +1118,7 @@ public class MetadataStandard implements Serializable { * Cycle may exist in metadata tree, so we have to keep trace of pair in process * of being compared for avoiding infinite recursion. */ - final ObjectPair pair = new ObjectPair(metadata1, metadata2); + final var pair = new ObjectPair(metadata1, metadata2); final Set<ObjectPair> inProgress = ObjectPair.CURRENT.get(); if (inProgress.add(pair)) { /* @@ -1115,9 +1154,9 @@ public class MetadataStandard implements Serializable { * * @see AbstractMetadata#hashCode() */ - public int hashCode(final Object metadata) throws ClassCastException { + public int hashCode(final Object metadata) { if (metadata != null) { - final Integer hash = HashCode.getOrCreate().walk(this, null, metadata, true); + final Integer hash = HashCode.getOrCreate().walk(this, Object.class, metadata, true); if (hash != null) return hash; /* * `hash` may be null if a cycle has been found. Example: A depends on B which depends on A, @@ -1133,6 +1172,8 @@ public class MetadataStandard implements Serializable { /** * Returns a string representation of this metadata standard. * This is for debugging purpose only and may change in any future version. + * + * @return a string representation for debugging purposes. */ @Override public String toString() { diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataVisitor.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataVisitor.java index 30349c3056..3b2b53fd44 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataVisitor.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/MetadataVisitor.java @@ -135,17 +135,25 @@ abstract class MetadataVisitor<R> { * <p>This method is typically invoked recursively while we iterate down the metadata tree. * It maintains a map of visited nodes for preventing the same node to be visited twice.</p> * + * <h4>Note about {@code baseType} argument when the type is unknown</h4> + * A null value for {@code baseType} is not equivalent to {@code Object.class}. + * If the value is null, this method tries to detect the interface automatically. + * If the value is {@code Object.class}, no detection is attempted, which is faster. + * In particular, if a {@code null} value would cause the default implementation of + * {@link AbstractMetadata#getStandardType()} to be invoked, then it is preferable + * to specify {@code Object.class} for avoiding to do reflection twice. + * * @param standard the metadata standard implemented by the object to visit. - * @param type the standard interface implemented by the metadata object, or {@code null} if unknown. + * @param baseType the standard interface implemented by the metadata object, or {@code null} if unknown. * @param metadata the metadata to visit. * @param mandatory {@code true} for throwing an exception for unsupported metadata type, or {@code false} for ignoring. * @return the value of {@link #result()} after all elements of the given metadata have been visited. * If the given metadata instance has already been visited, then this is the previously computed value. * If the computation is in progress (e.g. a cyclic graph), then this method returns {@code null}. */ - final R walk(final MetadataStandard standard, final Class<?> type, final Object metadata, final boolean mandatory) { + final R walk(final MetadataStandard standard, final Class<?> baseType, final Object metadata, final boolean mandatory) { if (!visited.containsKey(metadata)) { // Reminder: the associated value may be null. - final PropertyAccessor accessor = standard.getAccessor(new CacheKey(metadata.getClass(), type), mandatory); + final PropertyAccessor accessor = standard.getInstanceAccessor(metadata, baseType, mandatory); if (accessor != null) { final Filter filter = preVisit(accessor); // NONE, NON_EMPTY, WRITABLE or WRITABLE_RESULT. final boolean preconstructed; // Whether to write in instance provided by `result()`. diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ModifiableMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ModifiableMetadata.java index f57c6fafb3..b21e83387b 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ModifiableMetadata.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/ModifiableMetadata.java @@ -29,6 +29,7 @@ import java.nio.charset.Charset; import jakarta.xml.bind.annotation.XmlTransient; import org.opengis.util.CodeList; import org.opengis.metadata.Metadata; // For javadoc +import org.apache.sis.util.Classes; import org.apache.sis.util.collection.Containers; import org.apache.sis.metadata.internal.Resources; import org.apache.sis.system.Semaphores; @@ -351,7 +352,7 @@ public abstract class ModifiableMetadata extends AbstractMetadata { } else { copier = new MetadataCopier(getStandard()); } - final ModifiableMetadata md = (ModifiableMetadata) copier.copyRecursively(getInterface(), this); + final var md = (ModifiableMetadata) copier.copyRecursively(Classes.getRawClass(getStandardType()), this); if (target.code > EDITABLE) { md.transitionTo(target); } diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java index 8682869870..91029169c3 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/Pruner.java @@ -81,7 +81,7 @@ final class Pruner extends MetadataVisitor<Boolean> { final Pruner visitor = VISITORS.get(); final boolean p = visitor.prune; visitor.prune = prune; - final Boolean r = visitor.walk(metadata.getStandard(), metadata.getInterface(), metadata, false); + final Boolean r = visitor.walk(metadata.getStandard(), Object.class, metadata, false); visitor.prune = p; return (r != null) && r; // If there is a cycle (r == null), then the metadata is non-empty. } diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java index 68acb877f5..54bcd4aff2 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StandardImplementation.java @@ -72,7 +72,7 @@ final class StandardImplementation extends MetadataStandard { * But maybe the most interesting property is that it allocates less objects since {@code IdentityHashMap} * implementation doesn't need the chain of objects created by {@code HashMap}. */ - private transient Map<Class<?>,Class<?>> implementations; + private transient Map<Class<?>, Class<?>> implementations; /** * Creates a new instance working on implementation of interfaces defined in the specified package. @@ -82,7 +82,7 @@ final class StandardImplementation extends MetadataStandard { * @param interfacePackage the root package for metadata interfaces, with a trailing {@code '.'}. * @param implementationPackage the root package for metadata implementations. with a trailing {@code '.'}. * @param acronyms an array of (full text, acronyms) pairs. This array is not cloned. - * @param dependencies the dependencies to other metadata standards, or {@code null} if none. + * @param dependencies the dependencies to other metadata standards. */ StandardImplementation(final String citation, final String interfacePackage, final String implementationPackage, final String[] acronyms, final MetadataStandard... dependencies) @@ -141,7 +141,7 @@ final class StandardImplementation extends MetadataStandard { * package has been replaced by the implementation package, and some text * have been replaced by their acronym (if any). */ - final StringBuilder buffer = new StringBuilder(implementationPackage) + final var buffer = new StringBuilder(implementationPackage) .append(classname, interfacePackage.length(), classname.length()); if (acronyms != null) { for (int i=0; i<acronyms.length; i+=2) { diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StateChanger.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StateChanger.java index 69c2c50d35..5a0a8efc38 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StateChanger.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/StateChanger.java @@ -71,7 +71,7 @@ final class StateChanger extends MetadataVisitor<Boolean> { final StateChanger changer = VISITORS.get(); final ModifiableMetadata.State previous = changer.target; changer.target = target; - changer.walk(metadata.getStandard(), null, metadata, true); + changer.walk(metadata.getStandard(), Object.class, metadata, true); changer.target = previous; } diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java index 2261a1b70d..c92448097d 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNode.java @@ -209,8 +209,8 @@ class TreeNode implements Node { /** * 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. + * This key is used only for some default method implementations in the root node. + * Children will use the class of their node value instead. */ private CacheKey key() { return new CacheKey(metadata.getClass(), baseType); @@ -236,7 +236,7 @@ class TreeNode implements Node { * order to return the property identifier instead. */ String getIdentifier() { - final Class<?> type = table.standard.getInterface(key()); + final Class<?> type = table.standard.getInterface(key(), null); final String id = Types.getStandardName(type); return (id != null) ? id : Classes.getShortName(type); } @@ -259,7 +259,7 @@ class TreeNode implements Node { */ CharSequence getName() { return CharSequences.camelCaseToSentence(Classes.getShortName( - table.standard.getInterface(key()))).toString(); + table.standard.getInterface(key(), null))).toString(); } /** @@ -890,7 +890,7 @@ class TreeNode implements Node { * exists otherwise the call to `isLeaf()` above would have returned `true`. */ if (children == null || ((TreeNodeChildren) children).metadata != value) { - PropertyAccessor accessor = table.standard.getAccessor(new CacheKey(value.getClass(), baseType), true); + PropertyAccessor accessor = table.standard.getInstanceAccessor(value, baseType, true); children = new TreeNodeChildren(this, value, accessor); } } diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java index 92dca2a1f9..04538753ac 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/TreeNodeChildren.java @@ -77,10 +77,10 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> { /** * The accessor to use for accessing the property names, types and values of the {@link #metadata} object. - * This is given at construction time and shall be the same as the following code: + * This is given at construction time and shall be the equivalent to the following code: * * {@snippet lang="java" : - * accessor = parent.table.standard.getAccessor(metadata.getClass(), true); + * accessor = parent.table.standard.getTypeAccessor(metadata.getClass()); * } */ final PropertyAccessor accessor; diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/Merger.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/Merger.java index 77a04ef7a7..f2dac5a0e2 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/Merger.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/Merger.java @@ -166,8 +166,8 @@ public class Merger { * Get views of metadata as maps. Those maps are live: write operations * on those maps will be reflected on the metadata objects and conversely. */ - final Map<String,Object> targetMap = target.asMap(); - final Map<String,Object> sourceMap; + final Map<String, Object> targetMap = target.asMap(); + final Map<String, Object> sourceMap; if (source instanceof AbstractMetadata) { sourceMap = ((AbstractMetadata) source).asMap(); // Gives to subclasses a chance to override. } else { @@ -178,7 +178,7 @@ public class Merger { * If the value does not exist in the target map, then it can be copied directly. */ boolean success = true; - for (final Map.Entry<String,Object> entry : sourceMap.entrySet()) { + for (final Map.Entry<String, Object> entry : sourceMap.entrySet()) { final String propertyName = entry.getKey(); final Object sourceValue = entry.getValue(); final Object targetValue = dryRun ? targetMap.get(propertyName) @@ -212,8 +212,8 @@ public class Merger { * a ClassCastException is conform to this method contract). The loop tries to merge the * source elements to target elements that are specialized enough. */ - final Collection<?> targetList = (Collection<?>) targetValue; - final Collection<?> sourceList = new LinkedList<>((Collection<?>) sourceValue); + final var targetList = (Collection<?>) targetValue; + final var sourceList = new LinkedList<>((Collection<?>) sourceValue); for (final Object element : targetList) { if (element instanceof ModifiableMetadata) { final Iterator<?> it = sourceList.iterator(); diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/SecondaryTrait.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/SecondaryTrait.java deleted file mode 100644 index 0e5679625b..0000000000 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/internal/shared/SecondaryTrait.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.internal.shared; - -import java.lang.annotation.Target; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - - -/** - * Indicates that an interface implemented by a class is considered less significant compared to the primary interface. - * This annotation is used when a class implements two or more interfaces, but some of those interfaces can be ignored. - * This information is needed for identifying the main interface in metadata. - * - * <h2>Design note</h2> - * {@link ElementType#TYPE_USE} would be more appropriate. However, in the way that the metadata module currently uses - * this interface, an annotation on the class is more convenient (more straightforward code). - * - * @author Martin Desruisseaux (Geomatys) - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface SecondaryTrait { - /** - * The interface which is less significant compared to the primary interface. - * - * @return the less significant interface. - */ - Class<?> value(); -} diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java index d33e5e49e5..3357d29223 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/simple/SimpleMetadata.java @@ -77,9 +77,11 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati /** * Language(s) used for documenting metadata. * Also the language(s) used within the data. + * + * @return empty map. */ @Override - public Map<Locale,Charset> getLocalesAndCharsets() { + public Map<Locale, Charset> getLocalesAndCharsets() { return Collections.emptyMap(); } @@ -87,6 +89,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati * The scope or type of resource for which metadata is provided. * This method returns {@code this} for allowing call to {@link #getResourceScope()}. * + * @return empty collection. + * * @see #getResourceScope() * @see #getName() */ @@ -99,6 +103,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by default. This is part of the information * provided by {@link #getMetadataScopes()}. The {@code DATASET} default value is consistent with the fact that * {@code SimpleMetadata} implements {@link DataIdentification}. + * + * @return {@code DATASET}. */ @Override public ScopeCode getResourceScope() { @@ -107,6 +113,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati /** * Parties responsible for the metadata information. + * + * @return empty collection. */ @Override public Collection<Responsibility> getContacts() { @@ -115,6 +123,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati /** * Date(s) associated with the metadata. + * + * @return empty collection. */ @Override public Collection<CitationDate> getDateInfo() { @@ -123,8 +133,9 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati /** * Basic information about the resource(s) to which the metadata applies. - * This method returns {@code this} for allowing call to {@link #getCitation()}. - * and other methods. + * This method returns {@code this} for allowing call to {@link #getCitation()} and other methods. + * + * @return empty collection. * * @see #getCitation() * @see #getAbstract() @@ -151,6 +162,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati * Citation for the resource. * This is part of the information returned by {@link #getIdentificationInfo()}. * This method returns {@code this} for allowing call to {@link #getTitle()} and other methods. + * + * @return citation for the resource. */ @Override public Citation getCitation() { @@ -160,6 +173,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati /** * Brief narrative summary of the resource. * This is part of the information returned by {@link #getIdentificationInfo()}. + * + * @return null. */ @Override public InternationalString getAbstract() { @@ -171,6 +186,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati * This is part of the information returned by {@link #getIdentificationInfo()}. * Default implementation returns {@link SpatialRepresentationType#VECTOR}. * Subclasses should override this method if they represent gridded data instead of vector data. + * + * @return {@code VECTOR}. */ @Override public Collection<SpatialRepresentationType> getSpatialRepresentationTypes() { @@ -182,6 +199,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati * This is part of the information returned by {@link #getIdentificationInfo()}. * Default implementation returns {@link TopicCategory#LOCATION}. * Subclasses should override this method if they represent other kind of data. + * + * @return {@code LOCATION}. */ @Override public Collection<TopicCategory> getTopicCategories() { @@ -191,6 +210,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati /** * Spatial and temporal extent of the resource. * This is part of the information returned by {@link #getIdentificationInfo()}. + * + * @return empty collection. */ @Override public Collection<Extent> getExtents() { @@ -205,6 +226,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati /** * Name by which the cited resource is known. * This is part of the information returned by {@link #getCitation()}. + * + * @return null. */ @Override public InternationalString getTitle() { @@ -216,6 +239,8 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati * This is part of the information returned by {@link #getCitation()}. * Default implementation returns {@link PresentationForm#TABLE_DIGITAL}. * Subclasses should override this method if they represent other kind of data. + * + * @return empty collection. */ @Override public Collection<PresentationForm> getPresentationForms() { diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java index 7a9448e055..b1df00a083 100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/MetadataStandardTest.java @@ -151,15 +151,15 @@ public final class MetadataStandardTest extends TestCase { * Returns the interface type declared by the accessor for the given class. */ private Class<?> getAccessor(final Class<?> type, final boolean mandatory) { - final PropertyAccessor accessor = standard.getAccessor(new CacheKey(type), mandatory); + final PropertyAccessor accessor = standard.getTypeAccessor(type, mandatory); return (accessor != null) ? accessor.type : null; } /** - * Tests {@link MetadataStandard#getAccessor(CacheKey, boolean)}. + * Tests {@link MetadataStandard#getTypeAccessor(Class, boolean)}. */ @Test - public void testGetAccessor() { + public void testGetTypeAccessor() { standard = MetadataStandard.ISO_19115; assertEquals(Citation.class, getAccessor(DefaultCitation.class, true)); assertEquals(Completeness.class, getAccessor(AbstractCompleteness.class, true)); @@ -180,7 +180,7 @@ public final class MetadataStandardTest extends TestCase { */ @Test public void testGetWrongInterface() { - standard = new MetadataStandard("SIS", "org.apache.sis.dummy.", (MetadataStandard[]) null); + standard = new MetadataStandard("SIS", "org.apache.sis.dummy."); var e = assertThrows(ClassCastException.class, () -> getInterface(DefaultCitation.class)); assertMessageContains(e, "DefaultCitation"); } diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java index b38e966ce3..13784c2647 100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java @@ -151,7 +151,7 @@ public final class TreeNodeChildrenTest extends TestCase { final MetadataStandard standard = MetadataStandard.ISO_19115; final TreeTableView table = new TreeTableView(standard, citation, Citation.class, valuePolicy); final TreeNode node = (TreeNode) table.getRoot(); - final PropertyAccessor accessor = standard.getAccessor(new CacheKey(citation.getClass()), true); + final PropertyAccessor accessor = standard.getTypeAccessor(citation.getClass(), true); return new TreeNodeChildren(node, citation, accessor); } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java index 9898b90714..43076c6565 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AbstractIdentifiedObject.java @@ -540,11 +540,7 @@ public class AbstractIdentifiedObject extends FormattableObject implements Ident @Deprecated(since="1.7", forRemoval=true) // TODO: make package private. public final Class<? extends IdentifiedObject> getInterface() { - Type type = getStandardType(); - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getRawType(); - } - return (type instanceof Class<?>) ? (Class<? extends IdentifiedObject>) type : IdentifiedObject.class; + return Classes.getRawClass(getStandardType()).asSubclass(IdentifiedObject.class); } /** @@ -828,16 +824,11 @@ public class AbstractIdentifiedObject extends FormattableObject implements Ident /* * Fallback for non-SIS implementations. */ - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getRawType(); - } - if (type instanceof Class<?>) { - final Class<?> c = (Class<?>) type; - if (c.isInstance(object)) { - final Class<?>[] t = Classes.getLeafInterfaces(object.getClass(), c); - if (t.length == 1 && t[0] == type) { - return true; - } + final Class<?> c = Classes.getRawClass(type); + if (c != null && c.isInstance(object)) { + final Class<?>[] t = Classes.getLeafInterfaces(object.getClass(), c); + if (t.length == 1 && t[0] == type) { + return true; } } return false; @@ -1090,7 +1081,8 @@ public class AbstractIdentifiedObject extends FormattableObject implements Ident final Identifier id = identifier.getIdentifier(); if (id != null) { identifiers = Collections.singleton(id); - ScopedIdentifier<IdentifiedObject> key = new ScopedIdentifier<>(getInterface(), identifier.toString()); + Class<? extends IdentifiedObject> type = Classes.getStandardClass(this, IdentifiedObject.class); + ScopedIdentifier<IdentifiedObject> key = new ScopedIdentifier<>(type, identifier.toString()); key.store(IdentifiedObject.class, this, AbstractIdentifiedObject.class, "setIdentifier"); if (key != (key = key.rename(identifier.code))) { key.store(IdentifiedObject.class, this, null, null); // Shorter form without codespace. diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java index a66a30b765..efb262a864 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/Builder.java @@ -236,7 +236,7 @@ public abstract class Builder<B extends Builder<B>> { for (Class<?> c = expected; c != null; c = c.getSuperclass()) { Type type = c.getGenericSuperclass(); if (type instanceof ParameterizedType) { - final ParameterizedType p = (ParameterizedType) type; + final var p = (ParameterizedType) type; if (p.getRawType() == Builder.class) { type = p.getActualTypeArguments()[0]; if (type == expected) return true; @@ -273,8 +273,8 @@ public abstract class Builder<B extends Builder<B>> { this(); if (object != null) { properties.putAll(IdentifiedObjects.getProperties(object)); - final GenericName[] valueAlias = (GenericName[]) properties.remove(IdentifiedObject.ALIAS_KEY); - final Identifier[] valueIds = (Identifier[]) properties.remove(IdentifiedObject.IDENTIFIERS_KEY); + final var valueAlias = (GenericName[]) properties.remove(IdentifiedObject.ALIAS_KEY); + final var valueIds = (Identifier[]) properties.remove(IdentifiedObject.IDENTIFIERS_KEY); if (valueAlias != null) Collections.addAll(aliases, valueAlias); if (valueIds != null) Collections.addAll(identifiers, valueIds); } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java index ebfda81ba2..4558c0a7c4 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java @@ -48,7 +48,6 @@ import org.apache.sis.referencing.internal.PositionalAccuracyConstant; import org.apache.sis.referencing.internal.shared.WKTKeywords; import org.apache.sis.referencing.internal.shared.WKTUtilities; import org.apache.sis.metadata.internal.shared.NameToIdentifier; -import org.apache.sis.metadata.internal.shared.SecondaryTrait; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.Classes; import org.apache.sis.util.ComparisonMode; @@ -90,7 +89,6 @@ import org.opengis.referencing.datum.RealizationMethod; * * @since 1.5 */ -@SecondaryTrait(Datum.class) public class DefaultDatumEnsemble<D extends Datum> extends AbstractIdentifiedObject implements DatumEnsemble<D>, Datum { /** * Serial number for inter-operability with different versions. diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java index 75a28b7bd8..e909944bec 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultDatumEnsembleTest.java @@ -18,9 +18,13 @@ package org.apache.sis.referencing.datum; import java.util.Map; import java.util.List; +import org.opengis.metadata.Identifier; import org.opengis.referencing.datum.GeodeticDatum; -import org.apache.sis.metadata.iso.quality.DefaultAbsoluteExternalPositionalAccuracy; import org.apache.sis.referencing.GeodeticException; +import org.apache.sis.metadata.KeyNamePolicy; +import org.apache.sis.metadata.MetadataStandard; +import org.apache.sis.metadata.ValueExistencePolicy; +import org.apache.sis.metadata.iso.quality.DefaultAbsoluteExternalPositionalAccuracy; // Test dependencies import org.junit.jupiter.api.Test; @@ -28,6 +32,9 @@ import static org.junit.jupiter.api.Assertions.*; import static org.apache.sis.test.Assertions.assertMessageContains; import org.apache.sis.test.TestCase; +// Specific to the geoapi-3.1 and geoapi-4.0 branches: +import org.opengis.referencing.datum.DatumEnsemble; + /** * Tests the {@link DefaultDatumEnsemble} class. @@ -43,16 +50,23 @@ public class DefaultDatumEnsembleTest extends TestCase { } /** - * Tests the creation of a datum ensemble with some arbitrary geodetic datum. + * Creates a dummy ensemble with <abbr>WGS</abbr> datum. */ - @Test - public void testGeodetic() { - final DefaultDatumEnsemble<GeodeticDatum> ensemble = DefaultDatumEnsemble.create( - Map.of(DefaultDatumEnsemble.NAME_KEY, "Any WGS"), + private static DefaultDatumEnsemble<GeodeticDatum> WGS() { + return DefaultDatumEnsemble.create( + Map.of(DefaultDatumEnsemble.NAME_KEY, "Various WGS"), GeodeticDatum.class, List.of(HardCodedDatum.WGS84, HardCodedDatum.WGS72), new DefaultAbsoluteExternalPositionalAccuracy()); + } + /** + * Tests the creation of a datum ensemble with some arbitrary geodetic datum. + */ + @Test + public void testGeodetic() { + final DefaultDatumEnsemble<GeodeticDatum> ensemble = WGS(); + assertEquals("Various WGS", ensemble.getName().getCode()); assertTrue(ensemble.getMembers().contains(HardCodedDatum.WGS84)); assertTrue(ensemble.getMembers().contains(HardCodedDatum.WGS72)); final GeodeticDatum geodetic = assertInstanceOf(GeodeticDatum.class, ensemble); @@ -61,4 +75,25 @@ public class DefaultDatumEnsembleTest extends TestCase { GeodeticException e = assertThrows(GeodeticException.class, () -> geodetic.getEllipsoid()); assertMessageContains(e, "WGS"); } + + /** + * Tests the view as a map. This test depends on {@link DefaultDatumEnsemble#getStandardType()}, + * which is invoked by the metadata module for determining which interface is the main one. + */ + @Test + public void testValueMap() { + final Map<String, Object> values = MetadataStandard.ISO_19111.asValueMap( + WGS(), null, KeyNamePolicy.UML_IDENTIFIER, ValueExistencePolicy.ALL); + assertEquals("Various WGS", assertInstanceOf(Identifier.class, values.get("name")).getCode()); + } + + /** + * Tests another view as a map where the object is specified as a class instead of as an instance. + */ + @Test + public void testNameMap() { + final Map<String, String> names = MetadataStandard.ISO_19111.asNameMap( + DatumEnsemble.class, KeyNamePolicy.UML_IDENTIFIER, KeyNamePolicy.SENTENCE); + assertEquals("Name", names.get("name")); + } } diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java index 309f516221..6e7c4d4681 100644 --- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java +++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java @@ -41,9 +41,9 @@ import org.opengis.metadata.distribution.Format; import org.opengis.referencing.ReferenceSystem; import org.opengis.util.InternationalString; import org.apache.sis.io.TableAppender; -import org.apache.sis.metadata.simple.SimpleMetadata; import org.apache.sis.util.SimpleInternationalString; import org.apache.sis.util.iso.Types; +import org.apache.sis.metadata.simple.SimpleMetadata; import org.apache.sis.metadata.iso.citation.DefaultCitationDate; import org.apache.sis.metadata.iso.identification.DefaultKeywords; import org.apache.sis.metadata.iso.extent.Extents; @@ -56,8 +56,8 @@ import org.apache.sis.metadata.iso.citation.Citations; /** - * Information about the GPX file, author, and copyright restrictions. - * This is the root of the {@code <metadata>} element in a GPX file. + * Information about the <abbr>GPX</abbr> file, author, and copyright restrictions. + * This is the root of the {@code <metadata>} element in a <abbr>GPX</abbr> file. * At most one such element may appear in the document. * The XML content is like below: * diff --git a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java index 180cc537e9..3203e7f6d2 100644 --- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java +++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Store.java @@ -45,7 +45,7 @@ import org.opengis.feature.FeatureType; /** - * A data store backed by GPX files. + * A data store backed by <abbr>GPX</abbr> files. * This store does not cache the feature instances. * Any new {@linkplain #features(boolean) request for features} will re-read from the file. * diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java index 90f12301a7..13c3175627 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java @@ -293,11 +293,9 @@ public final class Classes { * Then replace (for example) `ParameterDescriptor<?>` by `ParameterDescriptor` * in order to get an instance of `Class` instead of other kind of `Type`. */ - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getRawType(); - } - if (type instanceof Class<?>) { - return changeArrayDimension((Class<?>) type, dimension); + final Class<?> element = getRawClass(type); + if (element != null) { + return changeArrayDimension(element, dimension); } } } @@ -322,6 +320,27 @@ public final class Classes { } } + /** + * Returns the raw class for the given generic type, or {@code null} if the type is not recognized. + * The current implementation recognizes instance of {@link Class} and {@link ParameterizedType}. + * + * <p><b>Purpose:</b> this method can be used for fetching the {@link Class} part + * from the value returned by {@link LenientComparable#getStandardType()}.</p> + * + * @param type type for which to get the raw type, or {@code null}. + * @return the raw class of the given type, or {@code null} if unknown. + * + * @see ParameterizedType#getRawType() + * @see LenientComparable#getStandardType() + * @since 1.7 + */ + public static Class<?> getRawClass(Type type) { + if (type instanceof ParameterizedType) { + type = ((ParameterizedType) type).getRawType(); + } + return (type instanceof Class<?>) ? (Class<?>) type : null; + } + /** * Returns the class of the specified object, or {@code null} if {@code object} is null. * This method is also useful for fetching the class of an object known only by its bound @@ -372,12 +391,9 @@ public final class Classes { */ public static <T> Class<? extends T> getStandardClass(final T object, final Class<T> baseType) { if (object instanceof LenientComparable) { - Type type = ((LenientComparable) object).getStandardType(); - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getRawType(); - } - if (type instanceof Class<?>) { - return ((Class<?>) type).asSubclass(baseType); + final Class<?> type = getRawClass(((LenientComparable) object).getStandardType()); + if (type != null) { + return type.asSubclass(baseType); } } Class<?> type = getClass(object); @@ -599,12 +615,12 @@ next: for (final Class<?> candidate : candidates) { /** * Returns the interfaces which are implemented by the two given classes. The returned set - * does not include the parent interfaces. For example if the two given objects implement the + * does not include the parent interfaces. For example, if the two given types extend the * {@link Collection} interface, then the returned set will contain the {@code Collection} * type but not the {@link Iterable} type, since it is implied by the collection type. * - * @param c1 the first class. - * @param c2 the second class. + * @param c1 the first class, or {@code null}. + * @param c2 the second class, or {@code null}. * @return the interfaces common to both classes, or an empty set if none. * Callers can freely modify the returned set. */ diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/LenientComparable.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/LenientComparable.java index 2f196c4039..dfd585384a 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/LenientComparable.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/LenientComparable.java @@ -124,7 +124,8 @@ public interface LenientComparable { * @return the type that defines the public properties of this instance. * * @see ComparisonMode#BY_CONTRACT - * @see Classes#getStandardClass(Object) + * @see Classes#getRawClass(Type) + * @see Classes#getStandardClass(Object, Class) * * @since 1.7 */
