sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1807488 - in /sis/branches/JDK8/core/sis-metadata/src: main/java/org/apache/sis/internal/metadata/Merger.java main/java/org/apache/sis/metadata/MetadataCopier.java test/java/org/apache/sis/internal/metadata/MergerTest.java
Date Wed, 06 Sep 2017 13:52:35 GMT
Author: desruisseaux
Date: Wed Sep  6 13:52:35 2017
New Revision: 1807488

URL: http://svn.apache.org/viewvc?rev=1807488&view=rev
Log:
Give some more control on the way to merge metadata elements in a collection.

Modified:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java?rev=1807488&r1=1807487&r2=1807488&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
[UTF-8] Wed Sep  6 13:52:35 2017
@@ -44,19 +44,19 @@ import org.apache.sis.util.Classes;
  *   <li>Otherwise if the target value is a collection, then:
  *     <ul>
  *       <li>For each element of the source collection, a corresponding element of
the target collection is searched.
- *         A pair of source and target elements is established if the pair meet all of the
following conditions:
+ *         A pair of source and target elements is established if the pair meets all of the
following conditions:
  *         <ul>
  *           <li>The {@linkplain MetadataStandard#getInterface(Class) standard type}
of the source element
  *               is assignable to the type of the target element.</li>
- *           <li>There is no conflict, i.e. no property value that are not collection
and not equal
- *               (note: this condition is disabled if {@link #avoidConflicts} is {@code false}).</li>
+ *           <li>There is no conflict, i.e. no property value that are not collection
and not equal.
+ *               This condition can be modified by overriding {@link #resolve(Object, ModifiableMetadata)}.</li>
  *         </ul>
  *         If such pair is found, then the merge operation if performed recursively
  *         for that pair of source and target elements.</li>
  *       <li>All other source elements will be added as new elements in the target
collection.</li>
  *     </ul>
  *   </li>
- *   <li>Otherwise the {@link #unmerged unmerged(…)} method is invoked.</li>
+ *   <li>Otherwise the {@link #copy(Object, ModifiableMetadata) copy(…)} method is
invoked.</li>
  * </ul>
  *
  * @author  Johann Sorel (Geomatys)
@@ -79,14 +79,6 @@ public class Merger {
     protected final Locale locale;
 
     /**
-     * {@code true} for performing a greater effort of avoiding merge conflicts.
-     * The default value is {@code false}, which may cause {@link #unmerged unmerged(…)}
to be invoked in
-     * situation where it could have been avoided. Setting this value to {@code true} increase
the chances
-     * of merge success at the expense of more computations.
-     */
-    public boolean avoidConflicts;
-
-    /**
      * Creates a new merger.
      *
      * @param  locale  the locale to use for formatting error messages, or {@code null} for
the default locale.
@@ -122,15 +114,15 @@ public class Merger {
      *         for example because the source class is a more specialized type than the target
class.
      * @throws IllegalArgumentException if this method detects a cross-reference between
source and target metadata.
      */
-    public final void merge(final Object source, final ModifiableMetadata target) {
-        if (!merge(source, target, false)) {
+    public final void copy(final Object source, final ModifiableMetadata target) {
+        if (!copy(source, target, false)) {
             throw new InvalidMetadataException(errors().getString(Errors.Keys.IllegalArgumentClass_3,
"target",
                     target.getStandard().getInterface(source.getClass()), Classes.getClass(target)));
         }
     }
 
     /**
-     * Implementation of {@link #merge(Object, ModifiableMetadata)} method,
+     * 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
@@ -139,7 +131,8 @@ public class Merger {
      * @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.
      */
-    private boolean merge(final Object source, final ModifiableMetadata target, final boolean
dryRun) {
+    @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.
@@ -191,7 +184,7 @@ public class Merger {
                                                : targetMap.putIfAbsent(propertyName, sourceValue);
             if (targetValue != null) {
                 if (targetValue instanceof ModifiableMetadata) {
-                    success = merge(sourceValue, (ModifiableMetadata) targetValue, dryRun);
+                    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
@@ -223,17 +216,22 @@ public class Merger {
                     for (final Object element : targetList) {
                         if (element instanceof ModifiableMetadata) {
                             final Iterator<?> it = sourceList.iterator();
-                            while (it.hasNext()) {
+distribute:                 while (it.hasNext()) {
                                 final Object value = it.next();
-                                if (!avoidConflicts || merge(value, (ModifiableMetadata)
element, true)) {
-                                    /*
-                                     * If enabled, above 'merge' call verified that the merge
can be done, including
-                                     * by recursive checks in all children. The intend is
to have a "all or nothing"
-                                     * behavior, before the 'merge' call below starts to
modify the values.
-                                     */
-                                    if (merge(value, (ModifiableMetadata) element, false))
{
+                                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 intend
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();
-                                        break;          // Merge at most one source element
to each target element.
+                                        break distribute;   // Merge at most one source element
to each target element.
                                     }
                                 }
                             }
@@ -263,8 +261,8 @@ public class Merger {
                     success = targetValue.equals(sourceValue);
                     if (!success) {
                         if (dryRun) break;
-                        unmerged(target, propertyName, sourceValue, targetValue);
-                        success = true;  // If no exception has been thrown by 'unmerged',
assume the conflict solved.
+                        merge(target, propertyName, sourceValue, targetValue);
+                        success = true;  // If no exception has been thrown by 'merged',
assume the conflict solved.
                     }
                 }
             }
@@ -278,19 +276,74 @@ public class Merger {
     }
 
     /**
-     * Invoked when a metadata value can not be merged.
+     * The action to perform when a <var>source</var> metadata element is about
to be written in an existing
+     * <var>target</var> element. Many metadata elements defined by ISO 19115
allows multi-occurrence, i.e.
+     * are stored in {@link Collection}. When a value <var>A</var> is about to
be added in an existing collection
+     * which already contains values <var>B</var> and <var>C</var>,
then different scenarios are possible.
+     *
+     * <p>For <var>A</var> ⟶ {<var>B</var>, <var>C</var>}:</p>
+     * <ul>
+     *   <li>Value <var>A</var> may overwrite some values of <var>B</var>.
This action is executed if
+     *       <code>{@linkplain Merger#resolve Merger.resolve}(A, B)</code> returns
{@link #MERGE}.</li>
+     *   <li>Value <var>A</var> may overwrite some values of <var>C</var>.
This action is executed if
+     *       <code>{@linkplain Merger#resolve Merger.resolve}(A, B)</code> returns
{@link #SEPARATE},
+     *       then {@code Merger.resolve(A, C)} returns {@link #MERGE}.</li>
+     *   <li>Value <var>A</var> may be added as a new value after <var>B</var>
and <var>C</var>.
+     *       This action is executed if <code>{@linkplain Merger#resolve Merger.resolve}(A,
B)</code>
+     *       <strong>and</strong> {@code Merger.resolve(A, C)} return {@link
#SEPARATE}.</li>
+     *   <li>Value <var>A</var> may be discarded. This action is executed
if
+     *       <code>{@linkplain Merger#resolve Merger.resolve}(A, B)</code>
+     *       <strong>or</strong> {@code Merger.resolve(A, C)} return {@link #IGNORE}.</li>
+     * </ul>
+     *
+     * @see Merger#resolve(Object, ModifiableMetadata)
+     */
+    public enum Resolution {
+        /**
+         * Indicates that <var>source</var> values should be written in <var>target</var>
attributes of existing
+         * metadata element. No new metadata object is created. If a value already exists
in the target metadata,
+         * then the {@link Merger#merge(ModifiableMetadata, String, Object, Object) merge(…)}
method will be invoked.
+         */
+        MERGE,
+
+        /**
+         * Indicates that <var>source</var> values should be written in another
metadata element.
+         */
+        SEPARATE,
+
+        /**
+         * Indicates that <var>source</var> values should be discarded.
+         */
+        IGNORE
+    }
+
+    /**
+     * Invoked when a source metadata element is about to be written in an existing target
element.
+     * The default implementation returns {@link Resolution#MERGE} if writing in the given
target
+     * would only fill holes, without overwriting any existing value. Otherwise this method
returns
+     * {@code Resolution#SEPARATE}.
+     *
+     * @param  source  the source metadata to copy.
+     * @param  target  where the source metadata would be copied if this method returns {@link
Resolution#MERGE}.
+     * @return {@link Resolution#MERGE} for writing {@code source} into {@code target}, or
+     *         {@link Resolution#SEPARATE} for writing {@code source} in a separated metadata
element, or
+     *         {@link Resolution#IGNORE} for discarding {@code source}.
+     */
+    protected Resolution resolve(Object source, ModifiableMetadata target) {
+        return copy(source, target, true) ? Resolution.MERGE : Resolution.SEPARATE;
+    }
+
+    /**
+     * Invoked when {@code Merger} can not merge a metadata value by itself.
      * The default implementation throws an {@link InvalidMetadataException}.
      * Subclasses can override this method if they want to perform a different processing.
      *
-     * <p><b>Tip:</b> to reduce the risks that this method is invoked,
consider setting the
-     * {@link #avoidConflicts} flag to {@code true} before invoking {@link #merge merge(…)}.</p>
-     *
      * @param target        the metadata instance in which the value should have been written.
      * @param propertyName  the name of the property to write.
      * @param sourceValue   the value to write.
      * @param targetValue   the value that already exist in the target metadata.
      */
-    protected void unmerged(ModifiableMetadata target, String propertyName, Object sourceValue,
Object targetValue) {
+    protected void merge(ModifiableMetadata target, String propertyName, Object sourceValue,
Object targetValue) {
         throw new InvalidMetadataException(errors().getString(Errors.Keys.ValueAlreadyDefined_1,
name(target, propertyName)));
     }
 }

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java?rev=1807488&r1=1807487&r2=1807488&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java
[UTF-8] Wed Sep  6 13:52:35 2017
@@ -47,8 +47,8 @@ import org.apache.sis.util.collection.Co
  * <p>This class is not thread-safe.
  * In multi-threads environment, each thread should use its own {@code MetadataCopier} instance.</p>
  *
- * <div class="section">Recommended alternative</div>
- * Deep metadata copies are sometime useful when using an existing metadata as a template.
+ * <div class="note"><b>Recommended alternative:</b>
+ * deep metadata copies are sometime useful when using an existing metadata as a template.
  * But the {@link ModifiableMetadata#unmodifiable()} method may provide a better way to use
a metadata as a template,
  * as it returns a snapshot and allows the caller to continue to modify the original metadata
object and create new
  * snapshots. Example:
@@ -77,6 +77,7 @@ import org.apache.sis.util.collection.Co
  * result of a call to {@link org.apache.sis.metadata.sql.MetadataSource#lookup(Class, String)})
into instances of the
  * public {@link AbstractMetadata} subclasses. But note that shallow copies as provided by
the {@code castOrCopy(…)}
  * static methods in each {@code AbstractMetadata} subclass are sometime sufficient.</p>
+ * </div>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8

Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java?rev=1807488&r1=1807487&r2=1807488&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
[UTF-8] Wed Sep  6 13:52:35 2017
@@ -100,8 +100,7 @@ public final strictfp class MergerTest e
         final DefaultMetadata source = createSample1();
         final DefaultMetadata target = createSample2();
         final Merger merger = new Merger(null);
-        merger.avoidConflicts = true;
-        merger.merge(source, target);
+        merger.copy(source, target);
 
         assertSetEquals(Arrays.asList(Locale.JAPANESE, Locale.FRENCH),  target.getLanguages());
         assertSetEquals(Collections.singleton(StandardCharsets.UTF_16), target.getCharacterSets());



Mime
View raw message