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;
}
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;
}
Aggregations