| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.metadata; |
| |
| import java.util.Set; |
| import java.util.Map; |
| import java.util.LinkedHashSet; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.io.ObjectInputStream; |
| import java.io.InvalidClassException; |
| import java.security.AccessController; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.metadata.citation.Citation; |
| import org.opengis.metadata.ExtendedElementInformation; |
| import org.apache.sis.util.Classes; |
| import org.apache.sis.util.ComparisonMode; |
| import org.apache.sis.util.collection.TreeTable; |
| import org.apache.sis.util.collection.CheckedContainer; |
| import org.apache.sis.internal.system.Modules; |
| import org.apache.sis.internal.system.Semaphores; |
| import org.apache.sis.internal.system.SystemListener; |
| import org.apache.sis.internal.simple.SimpleCitation; |
| import org.apache.sis.internal.util.FinalFieldSetter; |
| import org.apache.sis.internal.util.Strings; |
| |
| import static org.apache.sis.util.ArgumentChecks.ensureNonNull; |
| import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement; |
| |
| |
| /** |
| * Enumeration of some metadata standards. A standard is defined by a set of Java interfaces |
| * in a specific package or sub-packages. For example the {@linkplain #ISO_19115 ISO 19115} |
| * standard is defined by <a href="http://www.geoapi.org">GeoAPI</a> interfaces in the |
| * {@link org.opengis.metadata} package and sub-packages. |
| * |
| * <p>This class provides some methods operating on metadata instances through |
| * {@linkplain java.lang.reflect Java reflection}. The following rules are assumed:</p> |
| * |
| * <ul> |
| * <li>Metadata properties are defined by the collection of following getter methods found |
| * <strong>in the interface</strong>, ignoring implementation methods: |
| * <ul> |
| * <li>{@code get*()} methods with arbitrary return type;</li> |
| * <li>or {@code is*()} methods with boolean return type.</li> |
| * </ul></li> |
| * <li>All properties are <cite>readable</cite>.</li> |
| * <li>A property is also <cite>writable</cite> if a {@code set*(…)} method is defined |
| * <strong>in the implementation class</strong> for the corresponding getter method. |
| * The setter method doesn't need to be defined in the interface.</li> |
| * </ul> |
| * |
| * An instance of {@code MetadataStandard} is associated to every {@link AbstractMetadata} objects. |
| * The {@code AbstractMetadata} base class usually form the basis of ISO 19115 implementations but |
| * can also be used for other standards. |
| * |
| * <h2>Defining new {@code MetadataStandard} instances</h2> |
| * Users should use the pre-defined constants when applicable. |
| * However if new instances need to be defined, then there is a choice: |
| * |
| * <ul> |
| * <li>For <em>read-only</em> metadata, {@code MetadataStandard} can be instantiated directly. |
| * Only getter methods will be used and all operations that modify the metadata properties |
| * will throw an {@link UnmodifiableMetadataException}.</li> |
| * <li>For <em>read/write</em> metadata, the {@link #getImplementation(Class)} |
| * method must be overridden in a {@code MetadataStandard} subclass.</li> |
| * </ul> |
| * |
| * <h2>Thread safety</h2> |
| * The same {@code MetadataStandard} instance can be safely used by many threads without synchronization |
| * on the part of the caller. Subclasses shall make sure that any overridden methods remain safe to call |
| * from multiple threads, because the same {@code MetadataStandard} instances are typically referenced |
| * by a large amount of {@link ModifiableMetadata}. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * |
| * @see AbstractMetadata |
| * |
| * @since 0.3 |
| * @module |
| */ |
| public class MetadataStandard implements Serializable { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = 7549790450195184843L; |
| |
| /** |
| * {@code true} if implementations can alter the API defined in the interfaces by |
| * adding or removing properties. If {@code true}, then {@link PropertyAccessor} |
| * will check for {@link Deprecated} and {@link org.opengis.annotation.UML} |
| * annotations in the implementation classes in addition to the interfaces. |
| * |
| * <p>A value of {@code true} is useful when Apache SIS implements a newer standard |
| * than GeoAPI, but have a slight performance cost at construction time. Performance |
| * after construction should be the same.</p> |
| */ |
| static final boolean IMPLEMENTATION_CAN_ALTER_API = false; |
| |
| /** |
| * Metadata instances defined in this class. The current implementation does not yet |
| * contains the user-defined instances. However this may be something we will need to |
| * do in the future. |
| */ |
| private static final MetadataStandard[] INSTANCES; |
| |
| /** |
| * An instance working on ISO 19111 standard as defined by GeoAPI interfaces |
| * in the {@link org.opengis.referencing} package and sub-packages. |
| */ |
| public static final MetadataStandard ISO_19111; |
| |
| /** |
| * An instance working on ISO 19115 standard as defined by GeoAPI interfaces |
| * in the {@link org.opengis.metadata} package and sub-packages. |
| */ |
| public static final MetadataStandard ISO_19115; |
| |
| /** |
| * An instance working on ISO 19123 standard as defined by GeoAPI interfaces |
| * in the {@link org.opengis.coverage} package and sub-packages. |
| */ |
| public static final MetadataStandard ISO_19123; |
| static { |
| final String[] acronyms = {"CoordinateSystem", "CS", "CoordinateReferenceSystem", "CRS"}; |
| |
| // If new StandardImplementation instances are added below, please update StandardImplementation.readResolve(). |
| ISO_19115 = new StandardImplementation("ISO 19115", "org.opengis.metadata.", "org.apache.sis.metadata.iso.", null, null); |
| ISO_19111 = new StandardImplementation("ISO 19111", "org.opengis.referencing.", "org.apache.sis.referencing.", acronyms, new MetadataStandard[] {ISO_19115}); |
| ISO_19123 = new MetadataStandard ("ISO 19123", "org.opengis.coverage.", new MetadataStandard[] {ISO_19111}); |
| INSTANCES = new MetadataStandard[] { |
| ISO_19111, |
| ISO_19115, |
| ISO_19123 |
| }; |
| SystemListener.add(new SystemListener(Modules.METADATA) { |
| @Override protected void classpathChanged() { |
| clearCache(); |
| } |
| }); |
| } |
| |
| /** |
| * Bibliographical reference to the international standard. |
| * |
| * @see #getCitation() |
| */ |
| final Citation citation; |
| |
| /** |
| * The root package for metadata interfaces. Must have a trailing {@code '.'}. |
| */ |
| final String interfacePackage; |
| |
| /** |
| * The dependencies, or {@code null} if none. |
| * |
| * Note: the {@code null} value is for serialization compatibility. |
| */ |
| private final MetadataStandard[] dependencies; |
| |
| /** |
| * Accessors for the specified implementation classes. |
| * The only legal value types are: |
| * |
| * <ul> |
| * <li>{@link MetadataStandard} if type is handled by {@linkplain #dependencies} rather than this standard.</li> |
| * <li>{@link Class} if we found the interface for the type but did not yet created the {@link PropertyAccessor}.</li> |
| * <li>{@link PropertyAccessor} otherwise.</li> |
| * </ul> |
| */ |
| 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. |
| * |
| * <div class="note"><b>Example:</b>: For the ISO 19115 standard reflected by GeoAPI interfaces, |
| * {@code interfacePackage} shall be the {@link org.opengis.metadata} package.</div> |
| * |
| * @param citation bibliographical reference to the international standard. |
| * @param interfacePackage the root package for metadata interfaces. |
| * @param dependencies the dependencies to other metadata standards. |
| */ |
| public MetadataStandard(final Citation citation, final Package interfacePackage, MetadataStandard... dependencies) { |
| ensureNonNull("citation", citation); |
| ensureNonNull("interfacePackage", interfacePackage); |
| ensureNonNull("dependencies", dependencies); |
| this.citation = citation; |
| this.interfacePackage = interfacePackage.getName() + '.'; |
| this.accessors = new ConcurrentHashMap<>(); // Also defined in readObject(…) |
| if (dependencies.length == 0) { |
| this.dependencies = null; |
| } else { |
| this.dependencies = dependencies = dependencies.clone(); |
| for (int i=0; i<dependencies.length; i++) { |
| ensureNonNullElement("dependencies", i, dependencies[i]); |
| } |
| } |
| } |
| |
| /** |
| * Creates a new instance working on implementation of interfaces defined in the |
| * specified package. This constructor is used only for the pre-defined constants. |
| * |
| * @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. |
| */ |
| MetadataStandard(final String citation, final String interfacePackage, final MetadataStandard[] dependencies) { |
| this.citation = new SimpleCitation(citation); |
| this.interfacePackage = interfacePackage; |
| this.accessors = new ConcurrentHashMap<>(); |
| this.dependencies = dependencies; // No clone, since this constructor is for internal use only. |
| } |
| |
| /** |
| * Returns {@code true} if class or interface of the given name is supported by this standard. |
| * This method verifies if the class is a member of the package given at construction time or |
| * a sub-package. This method does not verify if the type is supported by a dependency. |
| * |
| * @param classname the name of the type to verify. |
| * @return {@code true} if the given type is supported by this standard. |
| */ |
| final boolean isSupported(final String classname) { |
| return classname.startsWith(interfacePackage); |
| } |
| |
| /** |
| * Returns the metadata standard for the given class. The argument given to this method can be |
| * either an interface defined by the standard, or a class implementing such interface. If the |
| * class implements more than one interface, then the first interface recognized by this method, |
| * in declaration order, will be retained. |
| * |
| * <p>The current implementation recognizes only the standards defined by the public static |
| * constants defined in this class. A future SIS version may recognize user-defined constants.</p> |
| * |
| * @param type the metadata standard interface, or an implementation class. |
| * @return the metadata standard for the given type, or {@code null} if not found. |
| */ |
| public static MetadataStandard forClass(final Class<?> type) { |
| String classname = type.getName(); |
| for (final MetadataStandard candidate : INSTANCES) { |
| if (candidate.isSupported(classname)) { |
| return candidate; |
| } |
| } |
| for (final Class<?> interf : Classes.getAllInterfaces(type)) { |
| classname = interf.getName(); |
| for (final MetadataStandard candidate : INSTANCES) { |
| if (candidate.isSupported(classname)) { |
| return candidate; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Clears the cache of accessors. This method is invoked when the classpath changed, |
| * in order to discard the references to classes that may need to be unloaded. |
| */ |
| static void clearCache() { |
| for (final MetadataStandard standard : INSTANCES) { |
| standard.accessors.clear(); |
| } |
| } |
| |
| /** |
| * Returns a bibliographical reference to the international standard. |
| * The default implementation return the citation given at construction time. |
| * |
| * @return bibliographical reference to the international standard. |
| */ |
| public Citation getCitation() { |
| return citation; |
| } |
| |
| /** |
| * Returns a key for use in {@link #getAccessor(CacheKey, boolean)} for the given type. |
| * The type may be an interface (typically a GeoAPI interface) or an implementation class. |
| */ |
| private CacheKey createCacheKey(Class<?> type) { |
| final Class<?> implementation = getImplementation(type); |
| if (implementation != null) { |
| type = implementation; |
| } |
| return new CacheKey(type); |
| } |
| |
| /** |
| * Returns the accessor for the specified implementation class, or {@code null} if none. |
| * The given class shall not be the standard interface, unless the metadata is read-only. |
| * More specifically, the given {@code type} shall be one of the following: |
| * |
| * <ul> |
| * <li>The value of {@code metadata.getClass()};</li> |
| * <li>The value of {@link #getImplementation(Class)} after check for non-null value.</li> |
| * </ul> |
| * |
| * @param key the implementation class together with the type declared by the property. |
| * @param mandatory whether this method shall throw an exception or return {@code null} |
| * 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}. |
| */ |
| 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(key); |
| if (value instanceof PropertyAccessor) { |
| return (PropertyAccessor) value; |
| } |
| /* |
| * Check if we started some computation that we can finish. A partial computation exists |
| * when we already found the Class<?> for the interface, but didn't created the accessor. |
| */ |
| final Class<?> type; |
| if (value instanceof Class<?>) { |
| type = (Class<?>) value; // Stored result of previous call to findInterface(…). |
| 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(key); |
| if (type == null) { |
| if (dependencies != null) { |
| for (final MetadataStandard dependency : dependencies) { |
| final PropertyAccessor accessor = dependency.getAccessor(key, false); |
| if (accessor != null) { |
| accessors.put(key, accessor); // Ok to overwrite existing instance here. |
| return accessor; |
| } |
| } |
| } |
| if (mandatory) { |
| throw new ClassCastException(key.unrecognized()); |
| } |
| return null; |
| } |
| } else { |
| throw new ClassCastException(key.invalid()); |
| } |
| /* |
| * Found the interface for which to create an accessor. Creates the accessor now, unless an accessor |
| * has been created concurrently in another thread in which case the later will be returned. |
| */ |
| return (PropertyAccessor) accessors.compute(key, (k, v) -> { |
| if (v instanceof PropertyAccessor) { |
| return v; |
| } |
| final Class<?> standardImpl = getImplementation(type); |
| final PropertyAccessor accessor; |
| if (SpecialCases.isSpecialCase(type)) { |
| accessor = new SpecialCases(type, k.type, standardImpl); |
| } else { |
| accessor = new PropertyAccessor(type, k.type, standardImpl); |
| } |
| return accessor; |
| }); |
| } |
| |
| /** |
| * Returns {@code true} if the given type is assignable to a type from this standard or one of its dependencies. |
| * If this method returns {@code true}, then invoking {@link #getInterface(Class)} is guaranteed to succeed |
| * without throwing an exception. |
| * |
| * @param type the implementation class (can be {@code null}). |
| * @return {@code true} if the given class is an interface of this standard, |
| * or implements an interface of this standard. |
| */ |
| public boolean isMetadata(final Class<?> type) { |
| return (type != null) && !type.isPrimitive() && 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(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(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. |
| * In such case, API that would normally be present in GeoAPI interfaces are temporarily available only in |
| * Apache SIS implementation classes. |
| */ |
| boolean isPendingAPI(final Class<?> type) { |
| return false; |
| } |
| |
| /** |
| * Returns the metadata interface implemented by the specified implementation. |
| * 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 key the standard interface or the implementation class. |
| * @return the single interface, or {@code null} if none where found. |
| */ |
| 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; |
| } |
| } |
| } |
| 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) { |
| /* |
| * 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. |
| */ |
| if (isPendingAPI(key.type)) { |
| return key.type; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * 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 Class<?> propertyType, final Collection<Class<?>> interfaces) { |
| for (final Class<?> candidate : type.getInterfaces()) { |
| if (propertyType.isAssignableFrom(candidate)) { |
| if (isSupported(candidate.getName())) { |
| interfaces.add(candidate); |
| } |
| getInterfaces(candidate, propertyType, interfaces); |
| } else if (IMPLEMENTATION_CAN_ALTER_API) { |
| /* |
| * If a GeoAPI interface is not assignable to the property type, maybe it is because the property type |
| * did not existed at the time current GeoAPI version was published. In such case, the implementation |
| * class may be a placeholder (pending API) for the not-yet-published GeoAPI interfaces. In that case |
| * we skip the `isAssignableFrom` check, but without recursive addition of parent interfaces since we |
| * would not know when to stop. |
| */ |
| if (isPendingAPI(propertyType)) { |
| if (isSupported(candidate.getName())) { |
| interfaces.add(candidate); |
| } |
| // No recursive call here. |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the metadata interface implemented by the specified implementation class. |
| * If the given type is already an interface from this standard, then it is returned |
| * unchanged. |
| * |
| * <div class="note"><b>Note:</b> |
| * The word "interface" may be taken in a looser sense than the usual Java sense because |
| * if the given type is defined in this standard package, then it is returned unchanged. |
| * The standard package is usually made of interfaces and code lists only, but this is |
| * not verified by this method.</div> |
| * |
| * @param <T> the compile-time {@code type}. |
| * @param type the implementation class. |
| * @return the interface implemented by the given implementation class. |
| * @throws ClassCastException if the specified implementation class does not implement an interface of this standard. |
| * |
| * @see AbstractMetadata#getInterface() |
| */ |
| public <T> Class<? super T> getInterface(final Class<T> type) throws ClassCastException { |
| ensureNonNull("type", type); |
| return getInterface(new CacheKey(type)); |
| } |
| |
| /** |
| * Implementation of {@link #getInterface(Class)} with the possibility to specify the property type. |
| * We do not provide the additional functionality of this method in public API on the assumption that |
| * users who want to invoke a {@code getInterface(…)} method does not know what that interface is. |
| * In Apache SIS case, we invoke this method when we almost know what the interface is but want to |
| * check if the actual value is a subtype. |
| * |
| * @see #isMetadata(CacheKey) |
| */ |
| @SuppressWarnings("unchecked") |
| final <T> Class<? super T> getInterface(final CacheKey key) throws ClassCastException { |
| final Class<?> interf; |
| final Object value = accessors.get(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(key); |
| } 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); |
| } |
| } |
| } |
| throw new ClassCastException(key.unrecognized()); |
| } |
| } else { |
| throw new ClassCastException(key.invalid()); |
| } |
| assert interf.isAssignableFrom(key.type) : key; |
| return (Class<? super T>) interf; |
| } |
| |
| /** |
| * Returns the implementation class for the given interface, or {@code null} if none. |
| * If non-null, the returned class must have a public no-argument constructor and the |
| * metadata instance created by that constructor must be initially empty (no default value). |
| * That no-argument constructor should never throw any checked exception. |
| * |
| * <p>The default implementation returns {@code null} in every cases. Subclasses shall |
| * override this method in order to map GeoAPI interfaces to their implementation.</p> |
| * |
| * @param <T> the compile-time {@code type}. |
| * @param type the interface, typically from the {@code org.opengis.metadata} package. |
| * @return the implementation class, or {@code null} if none. |
| */ |
| public <T> Class<? extends T> getImplementation(final Class<T> type) { |
| return null; |
| } |
| |
| /** |
| * Returns a value of the "title" property of the given metadata object. |
| * The title property is defined by {@link TitleProperty} annotation on the implementation class. |
| * |
| * @param metadata the metadata for which to get the title property, or {@code null}. |
| * @return the title property value of the given metadata, or {@code null} if none. |
| * |
| * @see TitleProperty |
| * @see ValueExistencePolicy#COMPACT |
| */ |
| final Object getTitle(final Object metadata) { |
| if (metadata != null) { |
| final Class<?> type = metadata.getClass(); |
| final PropertyAccessor accessor = getAccessor(createCacheKey(type), false); |
| if (accessor != null) { |
| TitleProperty an = type.getAnnotation(TitleProperty.class); |
| if (an != null || (an = accessor.implementation.getAnnotation(TitleProperty.class)) != null) { |
| return accessor.get(accessor.indexOf(an.name(), false), metadata); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the names of all properties defined in the given metadata type. |
| * The property names appears both as keys and as values, but may be written differently. |
| * The names may be {@linkplain KeyNamePolicy#UML_IDENTIFIER standard identifiers} (e.g. |
| * as defined by ISO 19115), {@linkplain KeyNamePolicy#JAVABEANS_PROPERTY JavaBeans names}, |
| * {@linkplain KeyNamePolicy#METHOD_NAME method names} or {@linkplain KeyNamePolicy#SENTENCE |
| * sentences} (usually in English). |
| * |
| * <div class="note"><b>Example:</b> |
| * The following code prints <code>"alternateTitle<u>s</u>"</code> (note the plural): |
| * |
| * {@preformat java |
| * MetadataStandard standard = MetadataStandard.ISO_19115; |
| * Map<String, String> names = standard.asNameMap(Citation.class, UML_IDENTIFIER, JAVABEANS_PROPERTY); |
| * String value = names.get("alternateTitle"); |
| * System.out.println(value); // alternateTitles |
| * } |
| * </div> |
| * |
| * The {@code keyPolicy} argument specify only the string representation of keys returned by the iterators. |
| * No matter the key name policy, the {@code key} argument given to any {@link Map} method can be any of the |
| * above-cited forms of property names. |
| * |
| * @param type the interface or implementation class of a metadata. |
| * @param keyPolicy determines the string representation of map keys. |
| * @param valuePolicy determines the string representation of map values. |
| * @return the names of all properties defined by the given metadata type. |
| * @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 |
| { |
| ensureNonNull("type", type); |
| ensureNonNull("keyPolicy", keyPolicy); |
| ensureNonNull("valuePolicy", valuePolicy); |
| return new NameMap(getAccessor(createCacheKey(type), true), keyPolicy, valuePolicy); |
| } |
| |
| /** |
| * Returns the type of all properties, or their declaring type, defined in the given metadata type. |
| * The keys in the returned map are the same than the keys in the above {@linkplain #asNameMap name map}. |
| * The values are determined by the {@code valuePolicy} argument, which can be |
| * {@linkplain TypeValuePolicy#ELEMENT_TYPE element type} or the |
| * {@linkplain TypeValuePolicy#DECLARING_INTERFACE declaring interface} among others. |
| * |
| * <div class="note"><b>Example:</b> |
| * the following code prints the {@link org.opengis.util.InternationalString} class name: |
| * |
| * {@preformat java |
| * MetadataStandard standard = MetadataStandard.ISO_19115; |
| * 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 |
| * } |
| * </div> |
| * |
| * @param type the interface or implementation class of a metadata. |
| * @param keyPolicy determines the string representation of map keys. |
| * @param valuePolicy whether the values shall be property types, the element types |
| * (same as property types except for collections) or the declaring interface or class. |
| * @return the types or declaring type of all properties defined in the given metadata type. |
| * @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 |
| { |
| ensureNonNull("type", type); |
| ensureNonNull("keyPolicy", keyPolicy); |
| ensureNonNull("valuePolicy", valuePolicy); |
| return new TypeMap(getAccessor(createCacheKey(type), true), keyPolicy, valuePolicy); |
| } |
| |
| /** |
| * Returns information about all properties defined in the given metadata type. |
| * The keys in the returned map are the same than the keys in the above |
| * {@linkplain #asNameMap name map}. The values contain information inferred from |
| * the ISO names, the {@link org.opengis.annotation.Obligation} enumeration and the |
| * {@link org.apache.sis.measure.ValueRange} annotations. |
| * |
| * <p>In the particular case of Apache SIS implementation, all values in the information map |
| * additionally implement the following interfaces:</p> |
| * <ul> |
| * <li>{@link Identifier} with the following properties: |
| * <ul> |
| * <li>The {@linkplain Identifier#getAuthority() authority} is this metadata standard {@linkplain #getCitation() citation}.</li> |
| * <li>The {@linkplain Identifier#getCodeSpace() codespace} is the standard name of the interface that contain the property.</li> |
| * <li>The {@linkplain Identifier#getCode() code} is the standard name of the property.</li> |
| * </ul> |
| * </li> |
| * <li>{@link CheckedContainer} with the following properties: |
| * <ul> |
| * <li>The {@linkplain CheckedContainer#getElementType() element type} is the type of property values |
| * as defined by {@link TypeValuePolicy#ELEMENT_TYPE}.</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * |
| * <div class="note"><b>Note:</b> |
| * the rational for implementing {@code CheckedContainer} is to consider each {@code ExtendedElementInformation} |
| * instance as the set of all possible values for the property. If the information had a {@code contains(E)} method, |
| * it would return {@code true} if the given value is valid for that property.</div> |
| * |
| * In addition, for each map entry the value returned by {@link ExtendedElementInformation#getDomainValue()} |
| * may optionally be an instance of any of the following classes: |
| * |
| * <ul> |
| * <li>{@link org.apache.sis.measure.NumberRange} if the valid values are constrained to some specific range.</li> |
| * </ul> |
| * |
| * @param type the metadata interface or implementation class. |
| * @param keyPolicy determines the string representation of map keys. |
| * @return information about all properties defined in the given metadata type. |
| * @throws ClassCastException if the given type does not implement a metadata interface of the expected package. |
| * |
| * @see org.apache.sis.metadata.iso.DefaultExtendedElementInformation |
| */ |
| public Map<String,ExtendedElementInformation> asInformationMap(final Class<?> type, final KeyNamePolicy keyPolicy) |
| throws ClassCastException |
| { |
| ensureNonNull("type", type); |
| ensureNonNull("keyNames", keyPolicy); |
| return new InformationMap(citation, getAccessor(createCacheKey(type), true), keyPolicy); |
| } |
| |
| /** |
| * Returns indices for all properties defined in the given metadata type. |
| * The keys in the returned map are the same than the keys in the above {@linkplain #asNameMap name map}. |
| * The values are arbitrary indices numbered from 0 inclusive to <var>n</var> exclusive, where <var>n</var> |
| * is the number of properties declared in the given metadata type. |
| * |
| * <p>Property indices may be used as an alternative to property names by some applications doing their own storage. |
| * Such index usages are fine for temporary storage during the Java Virtual Machine lifetime, but indices should not |
| * be used in permanent storage. The indices are stable as long as the metadata implementation does not change, |
| * but may change when the implementation is upgraded to a newer version.</p> |
| * |
| * @param type the interface or implementation class of a metadata. |
| * @param keyPolicy determines the string representation of map keys. |
| * @return indices of all properties defined by the given metadata type. |
| * @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 |
| { |
| ensureNonNull("type", type); |
| ensureNonNull("keyPolicy", keyPolicy); |
| return new IndexMap(getAccessor(createCacheKey(type), true), keyPolicy); |
| } |
| |
| /** |
| * Returns a view of the specified metadata object as a {@link Map}. |
| * The map is backed by the metadata object using Java reflection, so changes in the |
| * underlying metadata object are immediately reflected in the map and conversely. |
| * |
| * <p>The map content is determined by the arguments: {@code metadata} determines the set of |
| * keys, {@code keyPolicy} determines their {@code String} representations of those keys and |
| * {@code valuePolicy} determines whether entries having a null value or an empty collection |
| * shall be included in the map.</p> |
| * |
| * <h4>Supported operations</h4> |
| * The map supports the {@link Map#put(Object, Object) put(…)} and {@link Map#remove(Object) |
| * remove(…)} operations if the underlying metadata object contains setter methods. |
| * The {@code remove(…)} method is implemented by a call to {@code put(…, null)}. |
| * Note that whether the entry appears as effectively removed from the map or just cleared |
| * (i.e. associated to a null value) depends on the {@code valuePolicy} argument. |
| * |
| * <h4>Keys and values</h4> |
| * The keys are case-insensitive and can be either the JavaBeans property name, the getter method name |
| * or the {@linkplain org.opengis.annotation.UML#identifier() UML identifier}. The value given to a call |
| * to the {@code put(…)} method shall be an instance of the type expected by the corresponding setter method, |
| * or an instance of a type {@linkplain org.apache.sis.util.ObjectConverters#find(Class, Class) convertible} |
| * to the expected type. |
| * |
| * <h4>Multi-values entries</h4> |
| * Calls to {@code put(…)} replace the previous value, with one noticeable exception: if the metadata |
| * property associated to the given key is a {@link java.util.Collection} but the given value is a single |
| * element (not a collection), then the given value is {@linkplain java.util.Collection#add(Object) added} |
| * to the existing collection. In other words, the returned map behaves as a <cite>multi-values map</cite> |
| * for the properties that allow multiple values. If the intent is to unconditionally discard all previous |
| * values, then make sure that the given value is a collection when the associated metadata property expects |
| * such collection. |
| * |
| * <h4>Disambiguating instances that implement more than one metadata interface</h4> |
| * 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 Class<?> baseType, |
| final KeyNamePolicy keyPolicy, final ValueExistencePolicy valuePolicy) throws ClassCastException |
| { |
| ensureNonNull("metadata", metadata); |
| ensureNonNull("keyPolicy", keyPolicy); |
| ensureNonNull("valuePolicy", valuePolicy); |
| return new ValueMap(metadata, getAccessor(new CacheKey(metadata.getClass(), baseType), true), keyPolicy, valuePolicy); |
| } |
| |
| /** |
| * Returns the specified metadata object as a tree table. |
| * The tree table is backed by the metadata object using Java reflection, so changes in the |
| * underlying metadata object are immediately reflected in the tree table and conversely. |
| * |
| * <p>The returned {@code TreeTable} instance contains the following columns:</p> |
| * <ul class="verbose"> |
| * <li>{@link org.apache.sis.util.collection.TableColumn#IDENTIFIER}<br> |
| * The {@linkplain org.opengis.annotation.UML#identifier() UML identifier} if any, |
| * or the Java Beans property name otherwise, of a metadata property. For example |
| * in a tree table view of {@link org.apache.sis.metadata.iso.citation.DefaultCitation}, |
| * there is a node having the {@code "title"} identifier.</li> |
| * |
| * <li>{@link org.apache.sis.util.collection.TableColumn#INDEX}<br> |
| * If the metadata property is a collection, then the zero-based index of the element in that collection. |
| * Otherwise {@code null}. For example in a tree table view of {@code DefaultCitation}, if the |
| * {@code "alternateTitle"} collection contains two elements, then there is a node with index 0 |
| * for the first element and an other node with index 1 for the second element. |
| * |
| * <div class="note"><b>Note:</b> |
| * The {@code (IDENTIFIER, INDEX)} pair can be used as a primary key for uniquely identifying a node |
| * in a list of children. That uniqueness is guaranteed only for the children of a given node; |
| * the same keys may appear in the children of any other nodes.</div></li> |
| * |
| * <li>{@link org.apache.sis.util.collection.TableColumn#NAME}<br> |
| * A human-readable name for the node, derived from the identifier and the index. |
| * This is the column shown in the default {@link #toString()} implementation and |
| * may be localizable.</li> |
| * |
| * <li>{@link org.apache.sis.util.collection.TableColumn#TYPE}<br> |
| * The base type of the value (usually an interface).</li> |
| * |
| * <li>{@link org.apache.sis.util.collection.TableColumn#VALUE}<br> |
| * The metadata value for the node. Values in this column are writable if the underlying |
| * metadata class have a setter method for the property represented by the node.</li> |
| * |
| * <li>{@link org.apache.sis.util.collection.TableColumn#REMARKS}<br> |
| * Remarks or warning on the property value. This is rarely present. |
| * It is provided when the value may look surprising, for example the longitude values |
| * in a geographic bounding box spanning the anti-meridian.</li> |
| * </ul> |
| * |
| * <h4>Write operations</h4> |
| * Only the {@code VALUE} column may be writable, with one exception: newly created children need |
| * to have their {@code IDENTIFIER} set before any other operation. For example the following code |
| * adds a title to a citation: |
| * |
| * {@preformat java |
| * TreeTable.Node node = ...; // The node for a DefaultCitation. |
| * TreeTable.Node child = node.newChild(); |
| * child.setValue(TableColumn.IDENTIFIER, "title"); |
| * child.setValue(TableColumn.VALUE, "Le petit prince"); |
| * // Nothing else to do - the child node has been added. |
| * } |
| * |
| * Nodes can be removed by invoking the {@link java.util.Iterator#remove()} method on the |
| * {@linkplain org.apache.sis.util.collection.TreeTable.Node#getChildren() children} iterator. |
| * 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. |
| * |
| * <h4>Disambiguating instances that implement more than one metadata interface</h4> |
| * 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, Class<?> baseType, final ValueExistencePolicy valuePolicy) |
| throws ClassCastException |
| { |
| ensureNonNull("metadata", metadata); |
| ensureNonNull("valuePolicy", valuePolicy); |
| if (baseType == null) { |
| baseType = getInterface(metadata.getClass()); |
| } |
| return new TreeTableView(this, metadata, baseType, valuePolicy); |
| } |
| |
| /** |
| * Compares the two specified metadata objects. |
| * 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 <cite>shallow</cite> 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 later case, the final result is a deep comparison. |
| * |
| * @param metadata1 the first metadata object to compare. |
| * @param metadata2 the second metadata object to compare. |
| * @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. |
| * |
| * @see AbstractMetadata#equals(Object, ComparisonMode) |
| */ |
| public boolean equals(final Object metadata1, final Object metadata2, |
| final ComparisonMode mode) throws ClassCastException |
| { |
| if (metadata1 == metadata2) { |
| return true; |
| } |
| if (metadata1 == null || metadata2 == null) { |
| return false; |
| } |
| final Class<?> type1 = metadata1.getClass(); |
| final Class<?> type2 = metadata2.getClass(); |
| if (type1 != type2 && mode == ComparisonMode.STRICT) { |
| return false; |
| } |
| final PropertyAccessor accessor = getAccessor(new CacheKey(type1), true); |
| if (type1 != type2 && (!accessor.type.isAssignableFrom(type2) |
| || accessor.type != getAccessor(new CacheKey(type2), false).type)) |
| { |
| /* |
| * Note: the check for (accessor.type != getAccessor(…).type) would have been enough, but checking |
| * for isAssignableFrom(…) first can avoid the (relatively costly) creation of new PropertyAccessor. |
| */ |
| return false; |
| } |
| /* |
| * At this point, we have to perform the actual property-by-property comparison. |
| * Cycle may exist in metadata tree, so we have to keep trace of pair in process |
| * of being compared for avoiding infinite recursivity. |
| */ |
| final ObjectPair pair = new ObjectPair(metadata1, metadata2); |
| final Set<ObjectPair> inProgress = ObjectPair.CURRENT.get(); |
| if (inProgress.add(pair)) { |
| /* |
| * The NULL_COLLECTION semaphore prevents creation of new empty collections by getter methods |
| * (a consequence of lazy instantiation). The intent is to avoid creation of unnecessary objects |
| * for all unused properties. Users should not see behavioral difference, except if they override |
| * some getters with an implementation invoking other getters. However in such cases, users would |
| * have been exposed to null values at XML marshalling time anyway. |
| */ |
| final boolean allowNull = Semaphores.queryAndSet(Semaphores.NULL_COLLECTION); |
| try { |
| return accessor.equals(metadata1, metadata2, mode); |
| } finally { |
| inProgress.remove(pair); |
| if (!allowNull) { |
| Semaphores.clear(Semaphores.NULL_COLLECTION); |
| } |
| } |
| } else { |
| /* |
| * If we get here, a cycle has been found. Returns 'true' in order to allow the caller to continue |
| * comparing other properties. It is okay because someone else is comparing those two same objects, |
| * and that later comparison will do the actual check for property values. |
| */ |
| return true; |
| } |
| } |
| |
| /** |
| * Computes a hash code for the specified metadata. The hash code is defined as the sum |
| * of hash code values of all non-empty properties, plus the hash code of the interface. |
| * 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. |
| * |
| * @param metadata the metadata object to compute hash code. |
| * @return a hash code value for the specified metadata, or 0 if the given metadata is null. |
| * @throws ClassCastException if the metadata object does not implement a metadata interface of the expected package. |
| * |
| * @see AbstractMetadata#hashCode() |
| */ |
| public int hashCode(final Object metadata) throws ClassCastException { |
| if (metadata != null) { |
| final Integer hash = HashCode.getOrCreate().walk(this, null, 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, |
| * in which case the null value is returned for the second occurrence of A (not the first one). |
| * We can not compute a hash code value here, but it should be okay since that metadata is part |
| * of a bigger metadata object, and that enclosing object should have other properties for computing |
| * its hash code. |
| */ |
| } |
| return 0; |
| } |
| |
| /** |
| * Returns a string representation of this metadata standard. |
| * This is for debugging purpose only and may change in any future version. |
| */ |
| @Override |
| public String toString() { |
| return Strings.bracket(getClass(), citation.getTitle()); |
| } |
| |
| /** |
| * Assigns a {@link ConcurrentMap} instance to the given field. |
| * Used on deserialization only. |
| */ |
| static <T extends MetadataStandard> void setMapForField(final Class<T> classe, final T instance, final String name) |
| throws InvalidClassException |
| { |
| try { |
| AccessController.doPrivileged(new FinalFieldSetter<>(classe, name)).set(instance, new ConcurrentHashMap<>()); |
| } catch (ReflectiveOperationException e) { |
| throw FinalFieldSetter.readFailure(e); |
| } |
| } |
| |
| /** |
| * Invoked during deserialization for restoring the transient fields. |
| * |
| * @param in the input stream from which to deserialize a metadata standard. |
| * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data. |
| * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath. |
| */ |
| private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { |
| in.defaultReadObject(); |
| setMapForField(MetadataStandard.class, this, "accessors"); |
| } |
| } |