Search in sources :

Example 1 with InvalidMetadataException

use of org.apache.sis.metadata.InvalidMetadataException 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)

Example 2 with InvalidMetadataException

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

the class Extents method getGeographicBoundingBox.

/**
 * Returns a single geographic bounding box from the specified extent.
 * This method tries to find the bounding box in the cheapest way
 * before to fallback on more expansive computations:
 *
 * <ol>
 *   <li>First, this method searches geographic elements that are instance of {@link GeographicBoundingBox}.<ul>
 *     <li>If exactly one such instance is found, then this method returns that instance directly (no copy).</li>
 *     <li>If more than one instance is found, then this method computes and returns the
 *         {@linkplain DefaultGeographicBoundingBox#add union} of all bounding boxes.</li>
 *   </ul></li>
 *   <li>If above step found no {@code GeographicBoundingBox}, then this method inspects geographic elements
 *       that are instance of {@link BoundingPolygon}, taking in account only the envelopes associated to a
 *       coordinate reference system of kind {@link GeographicCRS}. If such envelopes are found, then this
 *       method computes and returns their union.</li>
 *   <li>If above step found no polygon's envelope associated to a geographic CRS, then in last resort this
 *       method uses all polygon's envelopes regardless their coordinate reference system (provided that the
 *       CRS is not null), applying coordinate transformations if needed.</li>
 *   <li>If above step found no polygon's envelope, then this method returns {@code null}.</li>
 * </ol>
 *
 * @param  extent  the extent to convert to a geographic bounding box, or {@code null}.
 * @return a geographic bounding box extracted from the given extent, or {@code null} if none.
 *
 * @see org.apache.sis.referencing.CRS#getDomainOfValidity(CoordinateReferenceSystem)
 */
public static GeographicBoundingBox getGeographicBoundingBox(final Extent extent) {
    GeographicBoundingBox bounds = null;
    if (extent != null) {
        DefaultGeographicBoundingBox modifiable = null;
        final List<Envelope> fallbacks = new ArrayList<>();
        for (final GeographicExtent element : extent.getGeographicElements()) {
            /*
                 * If a geographic bounding box can be obtained, add it to the previous boxes (if any).
                 * All exclusion boxes before the first inclusion box are ignored.
                 */
            if (element instanceof GeographicBoundingBox) {
                final GeographicBoundingBox item = (GeographicBoundingBox) element;
                if (bounds == null) {
                    /*
                         * We use DefaultGeographicBoundingBox.getInclusion(Boolean) below because
                         * add(…) method that we use cares about the case where inclusion is false.
                         */
                    if (DefaultGeographicBoundingBox.getInclusion(item.getInclusion())) {
                        bounds = item;
                    }
                } else {
                    if (modifiable == null) {
                        bounds = modifiable = new DefaultGeographicBoundingBox(bounds);
                    }
                    modifiable.add(item);
                }
            }
        }
        /*
             * If we found not explicit GeographicBoundingBox element, use the information that we
             * collected in BoundingPolygon elements. This may involve coordinate transformations.
             */
        if (bounds == null)
            try {
                for (final Envelope envelope : fallbacks) {
                    final DefaultGeographicBoundingBox item = new DefaultGeographicBoundingBox();
                    item.setBounds(envelope);
                    if (bounds == null) {
                        bounds = item;
                    } else {
                        if (modifiable == null) {
                            bounds = modifiable = new DefaultGeographicBoundingBox(bounds);
                        }
                        modifiable.add(item);
                    }
                }
            } catch (TransformException e) {
                throw new InvalidMetadataException(Errors.format(Errors.Keys.CanNotTransformEnvelope), e);
            }
    }
    return bounds;
}
Also used : InvalidMetadataException(org.apache.sis.metadata.InvalidMetadataException) ArrayList(java.util.ArrayList) TransformException(org.opengis.referencing.operation.TransformException) GeographicBoundingBox(org.opengis.metadata.extent.GeographicBoundingBox) Envelope(org.opengis.geometry.Envelope) GeographicExtent(org.opengis.metadata.extent.GeographicExtent)

Aggregations

InvalidMetadataException (org.apache.sis.metadata.InvalidMetadataException)2 ArrayList (java.util.ArrayList)1 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 MetadataStandard (org.apache.sis.metadata.MetadataStandard)1 ModifiableMetadata (org.apache.sis.metadata.ModifiableMetadata)1 CorruptedObjectException (org.apache.sis.util.CorruptedObjectException)1 Envelope (org.opengis.geometry.Envelope)1 GeographicBoundingBox (org.opengis.metadata.extent.GeographicBoundingBox)1 GeographicExtent (org.opengis.metadata.extent.GeographicExtent)1 TransformException (org.opengis.referencing.operation.TransformException)1