This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit eea0e6c4f6c337c270ccc6d7b7b83cf7095ed08d
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Sep 15 18:46:10 2022 +0200

    Allow change of `MergeStrategy` after resource creation.
    Use this new API in the JavaFX application.
---
 .../org/apache/sis/gui/dataset/ResourceItem.java   |  2 +
 .../aggregate/ConcatenatedGridCoverage.java        |  2 +-
 .../aggregate/ConcatenatedGridResource.java        | 96 ++++++++++++++--------
 .../sis/storage/aggregate/GroupAggregate.java      | 20 +++++
 .../sis/storage/aggregate/MergeStrategy.java       | 36 +++++++-
 5 files changed, 120 insertions(+), 36 deletions(-)

diff --git 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceItem.java
 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceItem.java
index f12d78fd6b..de7d55a133 100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceItem.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceItem.java
@@ -28,6 +28,7 @@ import javafx.scene.control.TreeItem;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.aggregate.MergeStrategy;
 import org.apache.sis.internal.storage.folder.UnstructuredAggregate;
 import org.apache.sis.internal.gui.DataStoreOpener;
 import org.apache.sis.internal.gui.BackgroundThreads;
@@ -390,6 +391,7 @@ final class ResourceItem extends TreeItem<Resource> {
                     case AGGREGATION: {
                         if (resource instanceof UnstructuredAggregate) {
                             result = ((UnstructuredAggregate) 
resource).getStructuredView();
+                            result = 
MergeStrategy.selectByTimeThenArea(null).update(result);
                         }
                         break;
                     }
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
index 31f799efcd..db43078431 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
@@ -196,7 +196,7 @@ final class ConcatenatedGridCoverage extends GridCoverage {
     }
 
     /**
-     * Returns {@code true} if the loading the coverage at the given index is 
deferred.
+     * Returns {@code true} if the loading of the coverage at the given index 
is deferred.
      * If {@code true},  then {@code slices[i]} shall be an instance of {@link 
GridCoverageResource}.
      * If {@code false}, then {@code slices[i]} shall be an instance of {@link 
GridCoverage}.
      */
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
index 903bc8f4a5..16398e76d2 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
@@ -82,12 +82,13 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
     /**
      * The slices of this resource, in the same order than {@link 
GridSliceLocator#sliceLows}.
      * Each slice is not necessarily 1 cell tick; larger slices are accepted.
+     * This array shall be read-only.
      */
     private final GridCoverageResource[] slices;
 
     /**
      * Whether loading of grid coverages should be deferred to rendering time.
-     * This is a bit set packed as {@code long} values. A bit value of 1 means
+     * This is a bit set packed as {@code int} values.  A bit value of 1 means
      * that the coverages at the corresponding index should be loaded from the
      * {@linkplain #slices} at same index only when first needed.
      *
@@ -98,10 +99,13 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
      *       In such case, we let the resource manages its own lazy 
loading.</li>
      * </ul>
      *
+     * This array shall be read-only. If changes are desired, a new array 
shall be created (copy-on-write).
+     *
+     * @see #isDeferred(int)
      * @see RasterLoadingStrategy#AT_READ_TIME
      * @see RasterLoadingStrategy#AT_RENDER_TIME
      */
-    private final long[] deferredLoading;
+    private int[] deferredLoading;
 
     /**
      * The object for identifying indices in the {@link #slices} array.
@@ -130,6 +134,7 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
 
     /**
      * The resolutions, or {@code null} if not yet computed. Can be an empty 
array after computation.
+     * Shall be read-only after computation.
      *
      * @see #getResolutions()
      */
@@ -159,7 +164,7 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
         this.slices           = slices;
         this.locator          = locator;
         this.strategy         = strategy;
-        this.deferredLoading  = new long[Numerics.ceilDiv(slices.length, 
Long.SIZE)];
+        this.deferredLoading  = new int[Numerics.ceilDiv(slices.length, 
Integer.SIZE)];
         for (final SampleDimension sd : ranges) {
             if (sd.forConvertedValues(true) != sd) {
                 isConverted = false;
@@ -169,6 +174,30 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
         isConverted = true;
     }
 
+    /**
+     * Creates a new resource with the same data than given resource but a 
different merge strategy.
+     * The two resources will share the same cache of loaded coverages.
+     *
+     * @param  source    the resource to copy.
+     * @param  strategy  the new merge strategy.
+     */
+    ConcatenatedGridResource(final ConcatenatedGridResource source, final 
MergeStrategy strategy) {
+        super(source.listeners, false);
+        synchronized (source) {
+            name                = source.name;
+            gridGeometry        = source.gridGeometry;
+            sampleDimensions    = source.sampleDimensions;
+            isConverted         = source.isConverted;
+            slices              = source.slices;
+            deferredLoading     = source.deferredLoading;
+            locator             = source.locator;
+            envelope            = source.envelope;
+            envelopeIsEvaluated = source.envelopeIsEvaluated;
+            resolutions         = source.resolutions;
+        }
+        this.strategy = strategy;
+    }
+
     /**
      * Modifies the name of the resource.
      * This information is used for metadata.
@@ -273,29 +302,23 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
      * @throws DataStoreException if an error occurred while fetching data 
store configuration.
      */
     @Override
-    public RasterLoadingStrategy getLoadingStrategy() throws 
DataStoreException {
+    public synchronized RasterLoadingStrategy getLoadingStrategy() throws 
DataStoreException {
         /*
          * If at least one bit of `deferredLoading` is set, then it means that
          * `setLoadingStrategy(…)` has been invoked with anything else than 
`AT_READ_TIME`.
          */
-        int  bitx = 0;
-        long mask = 1;
         RasterLoadingStrategy conservative = 
RasterLoadingStrategy.AT_GET_TILE_TIME;
-        for (final GridCoverageResource slice : slices) {
+        for (int i=0; i < slices.length; i++) {
+            final GridCoverageResource slice = slices[i];
             RasterLoadingStrategy s = slice.getLoadingStrategy();
             if (s == null || s.ordinal() == 0) {                    // Should 
never be null, but we are paranoiac.
-                s = ((deferredLoading[bitx] & mask) != 0)
-                        ? RasterLoadingStrategy.AT_RENDER_TIME
-                        : RasterLoadingStrategy.AT_READ_TIME;
+                s = isDeferred(i) ? RasterLoadingStrategy.AT_RENDER_TIME
+                                  : RasterLoadingStrategy.AT_READ_TIME;
             }
             if (s.ordinal() < conservative.ordinal()) {
                 conservative = s;
                 if (s.ordinal() == 0) break;
             }
-            if ((mask <<= 1) == 0) {
-                mask=1;
-                bitx++;
-            }
         }
         return conservative;
     }
@@ -309,25 +332,32 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
      * @throws DataStoreException if an error occurred while setting data 
store configuration.
      */
     @Override
-    public boolean setLoadingStrategy(final RasterLoadingStrategy strategy) 
throws DataStoreException {
+    public synchronized boolean setLoadingStrategy(final RasterLoadingStrategy 
strategy) throws DataStoreException {
         final boolean deferred = (strategy.ordinal() != 0);
-        Arrays.fill(deferredLoading, 0);
+        final int[] newValues = new int[deferredLoading.length];
         boolean accepted = true;
-        int  bitx = 0;
-        long mask = 1;
-        for (final GridCoverageResource slice : slices) {
+        for (int i=0; i < slices.length; i++) {
+            final GridCoverageResource slice = slices[i];
             if (!slice.setLoadingStrategy(strategy)) {
-                if (deferred) deferredLoading[bitx] |= mask;
+                if (deferred) {
+                    newValues[i >>> Numerics.INT_SHIFT] |= (1 << i);
+                }
                 accepted = false;
             }
-            if ((mask <<= 1) == 0) {
-                mask=1;
-                bitx++;
-            }
+        }
+        if (!Arrays.equals(deferredLoading, newValues)) {
+            deferredLoading = newValues;
         }
         return accepted;
     }
 
+    /**
+     * Returns {@code true} if the loading of the coverage at the given index 
is deferred.
+     */
+    private boolean isDeferred(final int i) {
+        return (deferredLoading[i >>> Numerics.INT_SHIFT] & (1 << i)) != 0;
+    }
+
     /**
      * Loads a subset of the grid coverage represented by this resource.
      *
@@ -361,16 +391,14 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
         final Object[]       coverages  = new Object[count];
         final GridGeometry[] geometries = new GridGeometry[count];
         int[] deferred = null;
-        int  bitx = lower >>> Numerics.LONG_SHIFT;
-        long mask = 1L << lower;                        // No need for (lower 
& 63) because high bits are ignored.
-        if (count <= 1) mask = 0;                       // Trick for forcing 
coverage loading.
         for (int i=0; i<count; i++) {
-            final GridCoverageResource slice = slices[lower + i];
+            final int j = lower + i;
+            final GridCoverageResource slice = slices[j];
             if (slice instanceof MemoryGridResource) {
                 final GridCoverage coverage = ((MemoryGridResource) 
slice).coverage;
                 coverages [i] = coverage;
                 geometries[i] = coverage.getGridGeometry();
-            } else if ((deferredLoading[bitx] & mask) == 0) {
+            } else if (!isDeferred(j) || count <= 1) {
                 final GridCoverage coverage = slice.read(domain, ranges);
                 coverages [i] = coverage;
                 geometries[i] = coverage.getGridGeometry();
@@ -382,18 +410,18 @@ final class ConcatenatedGridResource extends 
AbstractGridCoverageResource implem
                 }
                 deferred[i >>> Numerics.INT_SHIFT] |= (1 << i);
             }
-            if ((mask <<= 1) == 0) {
-                mask=1;
-                bitx++;
-            }
         }
         /*
-         * Following cast should never fail because the `mask = 0` trick 
ensures that we loaded the coverage.
+         * Following cast should never fail because the `count <= 1` check in 
above
+         * loop ensured that we loaded the coverage.
          * Otherwise (if more than one slice), create a concatenation of all 
slices.
          */
         if (count == 1) {
             return (GridCoverage) coverages[0];
         }
+        if (Arrays.equals(deferred, deferredLoading)) {
+            deferred = deferredLoading;                     // Slight memory 
saving for a common case.
+        }
         final GridGeometry union = locator.union(gridGeometry, 
Arrays.asList(geometries), GridGeometry::getExtent);
         return new ConcatenatedGridCoverage(this, union, coverages, lower, 
deferred, domain, ranges);
     }
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java
index d976fdaad8..1b7e9aa6eb 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java
@@ -114,6 +114,26 @@ final class GroupAggregate extends AbstractResource 
implements Aggregate, Aggreg
         this.name = name;
     }
 
+    /**
+     * Returns an aggregate with the same data than this aggregate but a 
different merge strategy.
+     */
+    final synchronized GroupAggregate update(final MergeStrategy strategy) {
+        boolean changed = false;
+        final GroupAggregate copy = new GroupAggregate(listeners, name, 
components.length);
+        for (int i=0; i < components.length; i++) {
+            final Resource component = components[i];
+            changed |= ((copy.components[i] = strategy.update(component)) != 
component);
+        }
+        if (!changed) {
+            return this;
+        }
+        copy.componentsAreLeaves = componentsAreLeaves;
+        copy.envelope            = envelope;
+        copy.envelopeIsEvaluated = envelopeIsEvaluated;
+        copy.sampleDimensions    = sampleDimensions;
+        return copy;
+    }
+
     /**
      * Sets all components of this aggregate to sub-aggregates, which are 
themselves initialized with the given filler.
      * This method may be invoked recursively if the sub-aggregates themselves 
have sub-sub-aggregates.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
index 82d7801cb1..14d4dd8c66 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
@@ -18,6 +18,7 @@ package org.apache.sis.storage.aggregate;
 
 import java.time.Instant;
 import java.time.Duration;
+import org.apache.sis.storage.Resource;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -49,6 +50,14 @@ import org.apache.sis.internal.util.Strings;
  * @module
  */
 public final class MergeStrategy {
+    /**
+     * Selects a single slice using criteria based first on temporal extent, 
then on geographic area.
+     * This default instance do not use any duration.
+     *
+     * @see #selectByTimeThenArea(Duration)
+     */
+    private static final MergeStrategy SELECT_BY_TIME = new 
MergeStrategy(null);
+
     /**
      * Temporal granularity of the time of interest, or {@code null} if none.
      * If non-null, intersections with TOI will be rounded to an integer 
amount of this granularity.
@@ -105,11 +114,15 @@ public final class MergeStrategy {
      *
      * If two slices are still considered equal after all above criteria, then 
an arbitrary one is selected.
      *
+     * <h4>Limitations</h4>
+     * Current implementation does not check the vertical dimension.
+     * This check may be added in a future version.
+     *
      * @param  timeGranularity  the temporal granularity of the Time of 
Interest (TOI), or {@code null} if none.
      * @return a merge strategy for selecting a slice based on temporal 
criteria first.
      */
     public static MergeStrategy selectByTimeThenArea(final Duration 
timeGranularity) {
-        return new MergeStrategy(timeGranularity);
+        return (timeGranularity != null) ? new MergeStrategy(timeGranularity) 
: SELECT_BY_TIME;
     }
 
     /**
@@ -141,6 +154,27 @@ public final class MergeStrategy {
         return selector.best();
     }
 
+    /**
+     * Updates the merge strategy of the specified resource.
+     * If the given resource is an instance created by {@link 
CoverageAggregator} and uses a different strategy,
+     * then a new resource using this merge strategy is returned. Otherwise 
the given resource is returned as-is.
+     * The returned resource will share the same resources and caches than the 
given resource.
+     *
+     * @param  resource  the resource for which to update the merge strategy, 
or {@code null}.
+     * @return resource with updated merge strategy, or {@code null} if the 
given resource was null.
+     */
+    public Resource update(Resource resource) {
+        if (resource instanceof ConcatenatedGridResource) {
+            final ConcatenatedGridResource c = (ConcatenatedGridResource) 
resource;
+            if (!equals(c.strategy)) {
+                resource = new ConcatenatedGridResource(c, this);
+            }
+        } else if (resource instanceof GroupAggregate) {
+            resource = ((GroupAggregate) resource).update(this);
+        }
+        return resource;
+    }
+
     /**
      * Returns a string representation of this strategy for debugging purposes.
      *

Reply via email to