Search in sources :

Example 1 with AbstractMetadata

use of org.apache.sis.metadata.AbstractMetadata in project sis by apache.

the class Merger method copy.

/**
 * Implementation of {@link #copy(Object, ModifiableMetadata)} method,
 * to be invoked recursively for all child properties to merge.
 *
 * @param  dryRun  {@code true} for executing the merge operation in "dry run" mode instead than performing the
 *                 actual merge. This mode is used for verifying if there is a merge conflict before to perform
 *                 the actual operation.
 * @return {@code true} if the merge operation is valid, or {@code false} if the given arguments are valid
 *         metadata but the merge operation can nevertheless not be executed because it could cause data lost.
 */
@SuppressWarnings("fallthrough")
private boolean copy(final Object source, final ModifiableMetadata target, final boolean dryRun) {
    /*
         * Verify if the given source can be merged with the target. If this is not the case, action
         * taken will depend on the caller: it may either skips the value or throws an exception.
         */
    final MetadataStandard standard = target.getStandard();
    if (!standard.getInterface(source.getClass()).isInstance(target)) {
        return false;
    }
    /*
         * Only after we verified that the merge operation is theoretically allowed, remember that
         * we are going to merge those two metadata and verify that we are not in an infinite loop.
         * We will also verify that the target metadata does not contain a source, or vis-versa.
         */
    {
        // For keeping 'sourceDone' and 'targetDone' more local.
        final Boolean sourceDone = done.put(source, Boolean.FALSE);
        final Boolean targetDone = done.put(target, Boolean.TRUE);
        if (sourceDone != null || targetDone != null) {
            if (Boolean.FALSE.equals(sourceDone) && Boolean.TRUE.equals(targetDone)) {
                /*
                     * At least, the 'source' and 'target' status are consistent. Pretend that we have already
                     * merged those metadata since actually the merge operation is probably underway by the caller.
                     */
                return true;
            } else {
                throw new IllegalArgumentException(errors().getString(Errors.Keys.CrossReferencesNotSupported));
            }
        }
    }
    /*
         * 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;
    if (source instanceof AbstractMetadata) {
        // Gives to subclasses a chance to override.
        sourceMap = ((AbstractMetadata) source).asMap();
    } else {
        sourceMap = standard.asValueMap(source, null, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
    }
    /*
         * Iterate on source values in order to find the objects that need to be copied or merged.
         * 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()) {
        final String propertyName = entry.getKey();
        final Object sourceValue = entry.getValue();
        final Object targetValue = dryRun ? targetMap.get(propertyName) : targetMap.putIfAbsent(propertyName, sourceValue);
        if (targetValue != null) {
            if (targetValue instanceof ModifiableMetadata) {
                success = copy(sourceValue, (ModifiableMetadata) targetValue, dryRun);
                if (!success) {
                    /*
                         * This exception may happen if the source is a subclass of the target. This is the converse
                         * of what we usually have in Java (we can assign a sub-type to a more generic Java variable)
                         * but happen here because if the source is a sub-type, we may not be able to copy all values
                         * from the source to the target. We do not use ClassCastException type in the hope to reduce
                         * confusion.
                         */
                    if (dryRun)
                        break;
                    throw new InvalidMetadataException(errors().getString(Errors.Keys.IllegalPropertyValueClass_3, name(target, propertyName), ((ModifiableMetadata) targetValue).getInterface(), Classes.getClass(sourceValue)));
                }
            } else if (targetValue instanceof Collection<?>) {
                /*
                     * If the merge is executed in dry run, there is no need to verify the collection elements since
                     * in case of conflict, it is always possible to append the source values as new elements at the
                     * end of the collection.
                     */
                if (dryRun)
                    continue;
                /*
                     * If the target value is a collection, then the source value should be a collection too
                     * (otherwise the two objects would not be implementing the same standard, in which case
                     * 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);
                for (final Object element : targetList) {
                    if (element instanceof ModifiableMetadata) {
                        final Iterator<?> it = sourceList.iterator();
                        distribute: while (it.hasNext()) {
                            final Object value = it.next();
                            switch(resolve(value, (ModifiableMetadata) element)) {
                                // case SEPARATE: do nothing.
                                case MERGE:
                                    {
                                        /*
                                         * If enabled, copy(…, true) call verified that the merge can be done, including
                                         * by recursive checks in all children. The intent is to have a "all or nothing"
                                         * behavior, before the copy(…, false) call below starts to modify the values.
                                         */
                                        if (!copy(value, (ModifiableMetadata) element, false))
                                            break;
                                    // Fall through
                                    }
                                case IGNORE:
                                    {
                                        it.remove();
                                        // Merge at most one source element to each target element.
                                        break distribute;
                                    }
                            }
                        }
                    }
                }
                /*
                     * Add remaining elements one-by-one. In such case, the Apache SIS metadata implementation
                     * shall add the elements to the collection instead than replacing the whole collection by
                     * a singleton. As a partial safety check, we verify that the collection instance contains
                     * all the previous values.
                     */
                for (final Object element : sourceList) {
                    final Object old = targetMap.put(propertyName, element);
                    if (old instanceof Collection<?>) {
                        final Collection<?> oldList = (Collection<?>) old;
                        if (oldList.size() <= targetList.size()) {
                            // Below is a more expansive check if assertions are enabled.
                            assert targetList.containsAll(oldList) : propertyName;
                            continue;
                        }
                    }
                    throw new InvalidMetadataException(errors().getString(Errors.Keys.UnsupportedImplementation_1, Classes.getShortClassName(targetList)));
                }
            } else {
                success = targetValue.equals(sourceValue);
                if (!success) {
                    if (dryRun)
                        break;
                    merge(target, propertyName, sourceValue, targetValue);
                    // If no exception has been thrown by 'merged', assume the conflict solved.
                    success = true;
                }
            }
        }
    }
    if (dryRun) {
        if (!Boolean.FALSE.equals(done.remove(source)) || !Boolean.TRUE.equals(done.remove(target))) {
            // Should never happen.
            throw new CorruptedObjectException();
        }
    }
    return success;
}
Also used : MetadataStandard(org.apache.sis.metadata.MetadataStandard) InvalidMetadataException(org.apache.sis.metadata.InvalidMetadataException) AbstractMetadata(org.apache.sis.metadata.AbstractMetadata) ModifiableMetadata(org.apache.sis.metadata.ModifiableMetadata) Iterator(java.util.Iterator) Collection(java.util.Collection) CorruptedObjectException(org.apache.sis.util.CorruptedObjectException) IdentityHashMap(java.util.IdentityHashMap) Map(java.util.Map)

Aggregations

Collection (java.util.Collection)1 IdentityHashMap (java.util.IdentityHashMap)1 Iterator (java.util.Iterator)1 Map (java.util.Map)1 AbstractMetadata (org.apache.sis.metadata.AbstractMetadata)1 InvalidMetadataException (org.apache.sis.metadata.InvalidMetadataException)1 MetadataStandard (org.apache.sis.metadata.MetadataStandard)1 ModifiableMetadata (org.apache.sis.metadata.ModifiableMetadata)1 CorruptedObjectException (org.apache.sis.util.CorruptedObjectException)1