| /* |
| * 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.EnumSet; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.List; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.lang.reflect.Constructor; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.Exceptions; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.util.collection.CodeListSet; |
| import org.apache.sis.metadata.internal.Resources; |
| |
| |
| /** |
| * Performs deep copies of given metadata instances. This class performs <em>copies</em>, not clones, |
| * since the copied metadata may not be instances of the same class as the original metadata. |
| * This class performs the following steps: |
| * |
| * <ul> |
| * <li>Get the {@linkplain MetadataStandard#getImplementation implementation class} of the given metadata instance.</li> |
| * <li>Create a {@linkplain Constructor#newInstance new instance} of the implementation class using the public no-argument constructor.</li> |
| * <li>Invoke all non-deprecated setter methods on the new instance with the corresponding value from the given metadata.</li> |
| * <li>If any of the values copied in above step is itself a metadata, recursively performs deep copy on those metadata instances too.</li> |
| * </ul> |
| * |
| * This copier may be used for converting metadata tree of unknown implementations (for example the result of a call to |
| * {@link org.apache.sis.metadata.sql.MetadataSource#lookup(Class, String)}) into instances of {@link AbstractMetadata}. |
| * The copier may also be used if a {@linkplain ModifiableMetadata.State#EDITABLE modifiable} metadata is desired after |
| * the original metadata has been made {@linkplain ModifiableMetadata.State#FINAL final}. |
| * |
| * <p>Default implementation copies all copiable children, regardless their {@linkplain ModifiableMetadata#state() state}. |
| * Static factory methods allow to construct some variants, for example skipping the copy of unmodifiable metadata instances |
| * since they can be safely shared.</p> |
| * |
| * <p>This class supports cyclic graphs in the metadata tree. It may return the given {@code metadata} object directly |
| * if the {@linkplain MetadataStandard#getImplementation implementation class} does not provide any setter method.</p> |
| * |
| * <p>This class is not thread-safe. |
| * In multi-threads environment, each thread should use its own {@code MetadataCopier} instance.</p> |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.2 |
| * @since 0.8 |
| */ |
| public class MetadataCopier extends MetadataVisitor<Object> { |
| /** |
| * The default metadata standard to use for object that are not {@link AbstractMetadata} instances, |
| * or {@code null} if none. |
| */ |
| private final MetadataStandard standard; |
| |
| /** |
| * The current metadata instance where to copy the property values. |
| */ |
| private Object target; |
| |
| /** |
| * Creates a new metadata copier. |
| * |
| * @param standard the default metadata standard to use for object that are not {@link AbstractMetadata} instances, |
| * or {@code null} if none. |
| */ |
| public MetadataCopier(final MetadataStandard standard) { |
| this.standard = standard; |
| } |
| |
| /** |
| * Returns the metadata standard to use for the given metadata object, or {@code null} if unknown. |
| */ |
| private MetadataStandard getStandard(final Object metadata) { |
| if (metadata instanceof AbstractMetadata) { |
| final MetadataStandard std = ((AbstractMetadata) metadata).getStandard(); |
| if (std != null) return std; |
| } |
| return standard; |
| } |
| |
| /** |
| * Creates a new metadata copier which avoid copying unmodifiable metadata. |
| * More specifically, any {@link ModifiableMetadata} instance in |
| * {@linkplain ModifiableMetadata.State#FINAL final state} will be kept <i>as-is</i>; |
| * those final metadata will not be copied since they can be safely shared. |
| * |
| * @param standard the default metadata standard to use for object that are not {@link AbstractMetadata} instances, |
| * or {@code null} if none. |
| * @return a metadata copier which skip the copy of unmodifiable metadata. |
| * |
| * @since 1.0 |
| */ |
| public static MetadataCopier forModifiable(final MetadataStandard standard) { |
| return new MetadataCopier(standard) { |
| @Override protected Object copyRecursively(final Class<?> type, final Object metadata) { |
| if (metadata instanceof ModifiableMetadata) { |
| final ModifiableMetadata.State state = ((ModifiableMetadata) metadata).state(); |
| if (state != null && state.isUnmodifiable()) { |
| return metadata; |
| } |
| } |
| return super.copyRecursively(type, metadata); |
| } |
| }; |
| } |
| |
| /** |
| * Performs a potentially deep copy of a metadata object of unknown type. |
| * The return value does not need to be of the same class as the argument. |
| * |
| * @param metadata the metadata object to copy, or {@code null}. |
| * @return a copy of the given metadata object, or {@code null} if the given argument is {@code null}. |
| * @throws UnsupportedOperationException if there is no implementation class for a metadata to copy, |
| * or an implementation class does not provide a public default constructor. |
| */ |
| public Object copy(final Object metadata) { |
| return copyRecursively(null, metadata); |
| } |
| |
| /** |
| * Performs a potentially deep copy of the given metadata object. |
| * This method is preferred to {@link #copy(Object)} when the type is known. |
| * The specified type should be an interface, not an implementation |
| * (for example {@link org.opengis.metadata.Metadata}, not |
| * {@link org.apache.sis.metadata.iso.DefaultMetadata}). |
| * |
| * @param <T> compile-time value of the {@code type} argument. |
| * @param type the interface of the metadata object to copy. |
| * @param metadata the metadata object to copy, or {@code null}. |
| * @return a copy of the given metadata object, or {@code null} if the given argument is {@code null}. |
| * @throws IllegalArgumentException if {@code type} is an implementation class instead of interface. |
| * @throws UnsupportedOperationException if there is no implementation class for a metadata to copy, |
| * or an implementation class does not provide a public default constructor. |
| */ |
| public <T> T copy(final Class<T> type, final T metadata) { |
| ArgumentChecks.ensureNonNull("type", type); |
| if (metadata instanceof AbstractMetadata) { |
| final Class<?> interfaceType = ((AbstractMetadata) metadata).getInterface(); |
| if (!type.isAssignableFrom(interfaceType)) { |
| /* |
| * In case the user specified an implementation despite the documentation warning. |
| * We could replace `type` by `interfaceType` and it would work most of the time, |
| * but we would still have some ClassCastExceptions for example if the given type |
| * is a java.lang.reflect.Proxy. It is probably better to let users know soon that |
| * they should specify an interface. |
| */ |
| throw new IllegalArgumentException(Resources.format(Resources.Keys.ExpectedInterface_2, interfaceType, type)); |
| } |
| } |
| return type.cast(copyRecursively(type, metadata)); |
| } |
| |
| /** |
| * Performs the actual copy operation on a single metadata instance. |
| * This method is invoked by all public {@code copy(…)} method with the root {@code metadata} |
| * object in argument, then is invoked recursively for all properties in that metadata object. |
| * If a metadata property is a collection, then this method is invoked for each element in the collection. |
| * |
| * <p>Subclasses can override this method if they need some control on the copy process.</p> |
| * |
| * @param type the interface of the metadata object to copy, or {@code null} if unspecified. |
| * @param metadata the metadata object to copy, or {@code null}. |
| * @return a copy of the given metadata object, or {@code null} if the given argument is {@code null}. |
| * @throws UnsupportedOperationException if there is no implementation class for a metadata to copy, |
| * or an implementation class does not provide a public default constructor. |
| */ |
| protected Object copyRecursively(final Class<?> type, final Object metadata) { |
| if (metadata != null) { |
| final MetadataStandard std = getStandard(metadata); |
| if (std != null) { |
| final Object result = walk(std, type, metadata, false); |
| if (result != null) { |
| return result; |
| } |
| } |
| } |
| return metadata; |
| } |
| |
| /** |
| * Invoked before the properties of a metadata instance are visited. This method creates a new instance, |
| * to be returned by {@link #result()}, and returns {@link Filter#WRITABLE_RESULT} for notifying the caller |
| * that write operations need to be performed on that {@code result} object. |
| */ |
| @Override |
| final Filter preVisit(final PropertyAccessor accessor) { |
| if (accessor.isWritable()) try { |
| target = accessor.implementation.getConstructor().newInstance(); |
| return Filter.WRITABLE_RESULT; |
| } catch (ReflectiveOperationException e) { |
| throw new UnsupportedOperationException(Errors.format(Errors.Keys.CanNotCopy_1, accessor.type), Exceptions.unwrap(e)); |
| } else { |
| target = null; |
| return Filter.NONE; |
| } |
| } |
| |
| /** |
| * Returns the metadata instance resulting from the copy. This method is invoked <strong>before</strong> |
| * metadata properties are visited. The returned value is a new, initially empty, metadata instance |
| * created by {@link #preVisit(PropertyAccessor)}. |
| */ |
| @Override |
| final Object result() { |
| return target; |
| } |
| |
| /** |
| * Verifies if the given metadata value is a map or a collection before to invoke |
| * {@link #copyRecursively(Class, Object)} for metadata elements. This method is |
| * invoked by {@link PropertyAccessor#walkWritable(MetadataVisitor, Object, Object)}. |
| */ |
| @Override |
| final Object visit(final Class<?> type, final Object metadata) { |
| if (!type.isInstance(metadata)) { |
| if (metadata instanceof Collection<?>) { |
| Collection<?> c = (Collection<?>) metadata; |
| if (c.isEmpty()) { |
| return null; |
| } |
| if (c instanceof EnumSet<?> || c instanceof CodeListSet<?>) { |
| /* |
| * Enum and CodeList elements cannot be cloned. Do not clone their collection neither; |
| * we presume that the setter method (to be invoked by reflection) will do that itself. |
| */ |
| } else { |
| final Object[] array = c.toArray(); |
| for (int i=0; i < array.length; i++) { |
| array[i] = copyRecursively(type, array[i]); |
| } |
| c = Arrays.asList(array); |
| if (metadata instanceof Set<?>) { |
| c = new LinkedHashSet<>(c); |
| } |
| } |
| return c; |
| } |
| /* |
| * Maps are rare in GeoAPI interfaces derived from ISO 19115. The main one |
| * is `Map<Locale,Charset>` returned by `Metadata.getLocalesAndCharsets()`. |
| * We cannot copy those entries because the `type` argument is `Map.Entry`, |
| * which is not enough information. Recursive copy should not be necessary |
| * anyway because we do not use `Map` for storing other metadata objects. |
| * We do not clone the map because it should be done by the setter method. |
| */ |
| if (metadata instanceof Map<?,?>) { |
| return metadata; |
| } |
| } |
| return copyRecursively(type, metadata); |
| } |
| |
| /** |
| * Returns the path to the currently copied property. |
| * Each element in the list is the UML identifier of a property. |
| * Element at index 0 is the name of the property of the root metadata object being copied. |
| * Element at index 1 is the name of a property which is a children of above property, <i>etc.</i> |
| * |
| * <p>The returned list is valid only during {@link #copyRecursively(Class, Object)} method execution. |
| * The content of this list become undetermined after the {@code copyRecursively} method returned.</p> |
| * |
| * @return the path to the currently copied property. |
| * |
| * @since 1.0 |
| */ |
| @Override |
| protected List<String> getCurrentPropertyPath() { |
| return super.getCurrentPropertyPath(); |
| } |
| } |