You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2026/06/13 16:39:19 UTC
(sis) 02/03: Replace an item list by an item map in the resource builder implementation. This is more convenient and more efficient when getting item by identifier.
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 53ca1f0dc8d33387fa5196282d5ddebd89bcff68
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Jun 13 15:35:06 2026 +0200
Replace an item list by an item map in the resource builder implementation.
This is more convenient and more efficient when getting item by identifier.
---
.../sis/storage/geoheif/ResourceBuilder.java | 187 +++++++++++++++------
1 file changed, 134 insertions(+), 53 deletions(-)
diff --git a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
index be195117b2..f3dfe6d0ec 100644
--- a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
+++ b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
@@ -17,12 +17,16 @@
package org.apache.sis.storage.geoheif;
import java.util.List;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.stream.Stream;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.io.IOException;
@@ -30,7 +34,6 @@ import javax.imageio.spi.ImageReaderSpi;
import org.opengis.util.GenericName;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.storage.UnsupportedEncodingException;
@@ -86,18 +89,42 @@ final class ResourceBuilder {
private final Map<Integer, ItemProperties.ForID> properties;
/**
- * Information about all resources, in the order the were found in the file.
- * Keys are resource identifiers, or 0 for the {@linkplain #primaryItem}.
- * Keys are {@link ItemInfoEntry#itemID} values of the associated map value.
- * Each list should contain only one item, but this class nevertheless accept many.
- * Note that some items may be members of a group such as a pyramid.
+ * Information about all resources in the order they were found in the file.
+ * Keys are {@link ItemInfoEntry#itemID} values of the associated map values.
+ * Key 0 is a special value meaning that the value is the {@link #primaryItem}.
+ *
+ * <p>Values shall be instances of {@link ItemInfoEntry} or {@code ItemInfoEntry[]}.
+ * Each entry should contain only one item, but this class nevertheless accept many.
+ * Note that some items may be members of a group such as a pyramid.</p>
+ *
+ * @see #info(Object)
*/
- private final Map<Integer, List<ItemInfoEntry>> itemInfos;
+ private final Map<Integer, Object> itemInfos;
+
+ /**
+ * Returns the given {@link #itemInfos} value as a list of {@link ItemInfoEntry} instances.
+ * The list should contain exactly one element, but may also be empty in case of missing value.
+ * List of more than one element are supported for avoiding to choose an arbitrary element.
+ */
+ private static List<ItemInfoEntry> info(final Object value) {
+ if (value == null) {
+ return Collections.emptyList();
+ } else if (value instanceof ItemInfoEntry) {
+ return Collections.singletonList((ItemInfoEntry) value);
+ } else {
+ return Arrays.asList((ItemInfoEntry[]) value);
+ }
+ }
/**
* References from the resources to other resources.
* The main usage is for a tiled image referencing each tile as an individual image.
* May be {@code null} if no such information was found.
+ *
+ * <p><b>Implementation note:</b>
+ * We perform linear search in this array for a specific value of {@link SingleItemTypeReference#fromItemID}.
+ * This is less efficient than using an hash map, but we assume that this array is short.
+ * Note that we also need to iterate over the array elements anyways.</p>
*/
private SingleItemTypeReference[] references;
@@ -114,17 +141,18 @@ final class ResourceBuilder {
private final Set<String> duplicatedBoxes;
/**
- * How to group the resources. If there is pyramids, they are declared here.
+ * How to group the resources. If this resource contains pyramids, these pyramids are declared here.
*/
private final List<GroupList> groups;
/**
- * Resources built by this class.
+ * Resources built by this class, in the order they were found in the file.
+ * Keys are {@link ItemInfoEntry#itemID} values.
*/
- private final List<Resource> resources;
+ private final Map<Integer, List<Resource>> itemResources;
/**
- * All data when no specific data where found in {@link #locations}.
+ * All data when no specific data where found in {@link #itemLocations}.
*/
private MediaData data;
@@ -151,7 +179,7 @@ final class ResourceBuilder {
builders = new HashMap<>();
duplicatedBoxes = new HashSet<>();
groups = new ArrayList<>();
- resources = new ArrayList<>();
+ itemResources = new LinkedHashMap<>();
for (final Box box : root.children) {
switch (box.type()) {
case MediaData.BOXTYPE: {
@@ -178,7 +206,12 @@ final class ResourceBuilder {
// Name, content type, etc.
case ItemInfo.BOXTYPE: {
for (final ItemInfoEntry entry : ((ItemInfo) box).entries) {
- itemInfos.computeIfAbsent(entry.itemID, (itemID) -> new ArrayList<ItemInfoEntry>(3)).add(entry);
+ itemInfos.merge(entry.itemID, entry, (current, value) -> {
+ final var more = (ItemInfoEntry) value;
+ return (current instanceof ItemInfoEntry)
+ ? new ItemInfoEntry[] {(ItemInfoEntry) current, more}
+ : ArraysExt.append((ItemInfoEntry[]) current, more);
+ });
}
break;
}
@@ -239,12 +272,9 @@ final class ResourceBuilder {
* @return a non-null item name.
*/
private String getResourceName(final int itemID) {
- final List<ItemInfoEntry> info = itemInfos.get(itemID);
- if (info != null) {
- for (ItemInfoEntry entry : info) {
- if (entry.itemName != null) {
- return entry.itemName;
- }
+ for (ItemInfoEntry entry : info(itemInfos.get(itemID))) {
+ if (entry.itemName != null) {
+ return entry.itemName;
}
}
return Integer.toUnsignedString(itemID);
@@ -284,20 +314,33 @@ final class ResourceBuilder {
// Only one case for now, but more cases may be added in the future.
case DerivedImageReference.BOXTYPE: {
for (final Integer toItem : items.toItemID) {
- final List<ItemInfoEntry> info = itemInfos.remove(toItem);
- if (info != null) {
- coverage.setTileBuilder(createImage(toItem, info, addTo));
- }
+ coverage.setTileBuilder(createImage(toItem, info(itemInfos.remove(toItem)), addTo));
}
}
break;
}
}
+ /**
+ * Creates the image for the item specified by the given identifier if the image sample model is supported.
+ * The resource may be an untiled image (item type {@code unci}) or a tiled image (item type {@code grid}).
+ *
+ * @param itemID identifier of the item to build as a coverage.
+ * @throws IOException if an error occurred while reading bytes from the input stream.
+ * @throws DataStoreException if another error occurred while building the image or resource.
+ */
+ private void createOptionalImage(final Integer itemID) throws DataStoreException, IOException {
+ try {
+ createImage(itemID, info(itemInfos.remove(itemID)), null);
+ } catch (UnsupportedEncodingException e) {
+ store.listeners().warning("A resource uses an unsupported sample model.", e);
+ }
+ }
+
/**
* Creates the image for the item specified by the given identifier.
* The resource may be an untiled image (item type {@code unci}) or a tiled image (item type {@code grid}).
- * If the {@code addTo} list is {@code null}, then the image is added to the {@linkplain #resources} list.
+ * If the {@code addTo} list is {@code null}, then the image is added to the {@link #itemResources} map.
*
* <p>The given {@code info} list should contain exactly one entry.
* This method nevertheless tries to be robust to cases where two or more entries exist,
@@ -305,8 +348,9 @@ final class ResourceBuilder {
*
* @param itemID identifier of the item to build as a coverage.
* @param info information about the item, normally as a singleton but other size are nevertheless accepted.
- * @param addTo a list where to add the image, or {@code null} for adding to {@link #resources} instead.
- * @return the builder used for building the image. If many images were created, returns the first builder.
+ * @param addTo a list where to add the image, or {@code null} for adding to {@link #itemResources} instead.
+ * @return the builder used for building the first image, or {@code null} if none.
+ * @throws IOException if an error occurred while reading bytes from the input stream.
* @throws DataStoreContentException if the "grid to <abbr>CRS</abbr>" transform or the sample dimensions cannot be created.
* @throws DataStoreException if another error occurred while building the image or resource.
*/
@@ -320,7 +364,13 @@ final class ResourceBuilder {
warning("The \"{0}\" resource is protected.", name);
continue;
}
- final int imageIndex = (addTo != null ? addTo : resources).size();
+ final int imageIndex;
+ if (addTo != null) {
+ imageIndex = addTo.size();
+ } else {
+ final List<Resource> resources = itemResources.get(entry.itemID);
+ imageIndex = (resources != null) ? resources.size() : 0;
+ }
final ItemProperties.ForID itemProperties = properties.remove(itemID);
final CoverageBuilder coverage = builders.computeIfAbsent(itemProperties,
(p) -> new CoverageBuilder(this, imageIndex, p, duplicatedBoxes));
@@ -350,7 +400,7 @@ final class ResourceBuilder {
*/
case ItemInfoEntry.GRID: {
if (references != null) {
- List<Image> tiles = null;
+ List<Image> tiles = null; // Wait to know the number of items before to create.
for (final SingleItemTypeReference items : references) {
if (items.fromItemID == entry.itemID) {
if (tiles == null && (tiles = addTo) == null) {
@@ -361,7 +411,7 @@ final class ResourceBuilder {
}
if (addTo == null && tiles != null && !tiles.isEmpty()) {
builders.remove(itemProperties); // Builder cannot be reused after resource creation.
- resources.add(coverage.build(name, tiles));
+ resources(entry.itemID).add(coverage.build(name, tiles));
}
}
continue;
@@ -400,7 +450,7 @@ final class ResourceBuilder {
addTo.add(image);
} else {
builders.remove(itemProperties); // Builder cannot be reused after resource creation.
- resources.add(coverage.build(name, image));
+ resources(entry.itemID).add(coverage.build(name, image));
}
}
}
@@ -416,21 +466,28 @@ final class ResourceBuilder {
*/
final Resource[] build() throws DataStoreException, IOException {
for (final PrimaryItem primary : primaryItem) {
- List<ItemInfoEntry> info = itemInfos.remove(primary.itemID);
- if (info == null) {
- info = itemInfos.remove(0); // `itemInfoEntry.itemID` = 0 means the primary item.
- if (info == null) continue;
+ List<ItemInfoEntry> info = info(itemInfos.remove(primary.itemID));
+ if (info.isEmpty()) {
+ info = info(itemInfos.remove(0)); // `itemInfoEntry.itemID` = 0 means the primary item.
+ if (info.isEmpty()) continue;
}
createImage(primary.itemID, info, null);
}
+ /*
+ * Create an image for all remaining items (items other than the primary item).
+ * We need to process first the items which contain references to other items,
+ * because they may be tiled images referencing their tiles.
+ */
+ if (references != null) {
+ for (final SingleItemTypeReference items : references) {
+ if (items.type() == DerivedImageReference.BOXTYPE) {
+ createOptionalImage(items.fromItemID);
+ }
+ }
+ }
// Iterate over a snapshot because elements may be removed by `createImage(…)`.
for (final Integer itemID : itemInfos.keySet().toArray(Integer[]::new)) {
- final List<ItemInfoEntry> info = itemInfos.remove(itemID);
- if (info != null) try {
- createImage(itemID, info, null);
- } catch (UnsupportedEncodingException e) {
- store.listeners().warning("A resource uses an unsupported sample model.", e);
- }
+ createOptionalImage(itemID);
}
/*
* At this point, all resources have either been created or discarded.
@@ -440,25 +497,49 @@ final class ResourceBuilder {
for (Box child : box.children) {
if (child instanceof EntityToGroup group) { // Should be the type of all children.
final GenericName name = store.createComponentName(getResourceName(group.groupID));
- var grids = new GridCoverageResource[group.entityID.length];
- int count = 0;
+ final var components = new ArrayList<ImageResource>(group.entityID.length);
for (int entityID : group.entityID) {
- // TODO: wrong class
- var grid = (GridCoverageResource) itemInfos.remove(entityID);
- if (grid != null) grids[count++] = grid;
+ final Iterator<Resource> it = itemResources.getOrDefault(entityID, List.of()).iterator();
+ while (it.hasNext()) {
+ final Resource grid = it.next();
+ if (grid instanceof ImageResource) {
+ components.add((ImageResource) grid);
+ it.remove();
+ }
+ }
}
- grids = ArraysExt.resize(grids, count);
final Resource resource;
- if (child instanceof ImagePyramid pyramid) {
- new Pyramid(store, name, pyramid, grids); // TODO
- continue;
- } else {
- resource = new Group(store, name, grids);
+ switch (components.size()) {
+ case 0: continue;
+ case 1: resource = components.get(0); break;
+ default: {
+ final var grids = components.toArray(ImageResource[]::new);
+ if (child instanceof ImagePyramid pyramid) {
+ resource = new Pyramid(store, name, pyramid, grids);
+ } else {
+ resource = new Group(store, name, grids);
+ }
+ }
}
- resources.add(resource);
+ resources(group.groupID).add(resource);
}
}
}
- return resources.toArray(Resource[]::new);
+ // Concatenate the collection of lists to a single list.
+ return itemResources.values().stream()
+ .map(List::stream)
+ .reduce(Stream::concat)
+ .orElse(Stream.empty())
+ .toArray(Resource[]::new);
+ }
+
+ /**
+ * Returns the resource for the given item identifier.
+ *
+ * @param itemID item identifier for which to get the resources.
+ * @return modifiable list of resources for the given identifier.
+ */
+ private List<Resource> resources(final int itemID) {
+ return itemResources.computeIfAbsent(itemID, (key) -> new ArrayList<>());
}
}