use of org.apache.sis.metadata.ModifiableMetadata 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;
}
Aggregations