You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by am...@apache.org on 2021/03/24 13:13:02 UTC
[sis] branch refactor/strict_storage_connector updated (8572c20 ->
0eb0cd8)
This is an automated email from the ASF dual-hosted git repository.
amanin pushed a change to branch refactor/strict_storage_connector
in repository https://gitbox.apache.org/repos/asf/sis.git.
from 8572c20 fix(Storage): improve closing logic of strict storage connector.
new eb8954b chore(Storage): mirror Storage connector API in strict version
new 0eb0cd8 chore(Storage): switch Strict storage connector class with original one.
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../sis/storage/netcdf/NetcdfStoreProvider.java | 13 +-
.../org/apache/sis/storage/StorageConnector.java | 1647 +++++---------------
.../apache/sis/storage/StrictStorageConnector.java | 455 ------
...{StorageConnector.java => UnsafeConnector.java} | 90 +-
.../apache/sis/storage/StorageConnectorTest.java | 48 +-
.../sis/storage/StrictStorageConnectorTest.java | 30 +-
6 files changed, 461 insertions(+), 1822 deletions(-)
delete mode 100644 storage/sis-storage/src/main/java/org/apache/sis/storage/StrictStorageConnector.java
copy storage/sis-storage/src/main/java/org/apache/sis/storage/{StorageConnector.java => UnsafeConnector.java} (95%)
[sis] 02/02: chore(Storage): switch Strict storage connector class
with original one.
Posted by am...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
amanin pushed a commit to branch refactor/strict_storage_connector
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 0eb0cd8bd5793492a221175f837c564c6a12cabc
Author: Alexis Manin <al...@geomatys.com>
AuthorDate: Wed Mar 24 14:11:45 2021 +0100
chore(Storage): switch Strict storage connector class with original one.
---
.../sis/storage/netcdf/NetcdfStoreProvider.java | 13 +-
.../org/apache/sis/storage/StorageConnector.java | 1647 +++++---------------
.../apache/sis/storage/StrictStorageConnector.java | 497 ------
...{StorageConnector.java => UnsafeConnector.java} | 90 +-
.../apache/sis/storage/StorageConnectorTest.java | 48 +-
.../sis/storage/StrictStorageConnectorTest.java | 30 +-
6 files changed, 461 insertions(+), 1864 deletions(-)
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
index 7e8bea4..cc8cfae 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
@@ -27,7 +27,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
-import org.apache.sis.storage.StrictStorageConnector;
+import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.UnsupportedStorageException;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.apache.sis.internal.netcdf.Decoder;
@@ -44,7 +44,6 @@ import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.setup.OptionKey;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreProvider;
-import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.storage.FeatureSet;
@@ -182,10 +181,6 @@ public class NetcdfStoreProvider extends DataStoreProvider {
*/
@Override
public ProbeResult probeContent(final StorageConnector connector) throws DataStoreException {
- return probeContentStrict(new StrictStorageConnector(connector));
- }
-
- private ProbeResult probeContentStrict(final StrictStorageConnector connector) throws DataStoreException {
int version = 0;
boolean hasVersion = false;
boolean isSupported = false;
@@ -292,12 +287,6 @@ public class NetcdfStoreProvider extends DataStoreProvider {
* @throws DataStoreException if a logical error (other than I/O) occurred.
*/
static Decoder decoder(final StoreListeners listeners, final StorageConnector connector) throws IOException, DataStoreException {
- return decoder(listeners, new StrictStorageConnector(connector));
- }
-
- static Decoder decoder(final StoreListeners listeners, final StrictStorageConnector connector)
- throws IOException, DataStoreException
- {
final GeometryLibrary geomlib = connector.getOption(OptionKey.GEOMETRY_LIBRARY);
Decoder decoder;
Class<?> viewType;
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
index 90bae45..9b6eca4 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
@@ -1,1394 +1,509 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
package org.apache.sis.storage;
-import java.util.Map;
-import java.util.Iterator;
-import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.io.Reader;
-import java.io.DataInput;
-import java.io.InputStream;
+import java.io.Closeable;
+import java.io.File;
import java.io.IOException;
-import java.io.LineNumberReader;
-import java.io.InputStreamReader;
-import java.io.BufferedInputStream;
-import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.net.URI;
import java.nio.ByteBuffer;
-import java.nio.channels.Channel;
-import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
-import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.function.Function;
import javax.imageio.stream.ImageInputStream;
-import javax.imageio.ImageIO;
-import java.sql.Connection;
-import java.sql.SQLException;
import javax.sql.DataSource;
-import org.apache.sis.util.Debug;
-import org.apache.sis.util.Classes;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ObjectConverters;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.logging.Logging;
-import org.apache.sis.util.collection.TreeTable;
-import org.apache.sis.util.collection.TableColumn;
-import org.apache.sis.util.collection.DefaultTreeTable;
-import org.apache.sis.util.UnconvertibleObjectException;
-import org.apache.sis.internal.storage.Resources;
-import org.apache.sis.internal.storage.StoreUtilities;
-import org.apache.sis.internal.storage.io.IOUtilities;
-import org.apache.sis.internal.storage.io.ChannelFactory;
import org.apache.sis.internal.storage.io.ChannelDataInput;
-import org.apache.sis.internal.storage.io.ChannelImageInputStream;
-import org.apache.sis.internal.storage.io.InputStreamAdapter;
-import org.apache.sis.internal.storage.io.RewindableLineReader;
-import org.apache.sis.internal.util.Strings;
-import org.apache.sis.io.InvalidSeekException;
import org.apache.sis.setup.OptionKey;
-
+import org.apache.sis.util.UnconvertibleObjectException;
+import org.apache.sis.util.collection.BackingStoreException;
/**
- * Information for creating a connection to a {@link DataStore} in read and/or write mode.
- * {@code StorageConnector} wraps an input {@link Object}, which can be any of the following types:
+ * Extension of a storage connector providing strong encapsulation of "views". This allows to:
+ * <ul>
+ * <li>Adopt a <em>fail-fast</em> behavior in case storage view is corrupted by a user</li>
+ * <li>
+ * Provide easier usage:
+ * <ul>
+ * <li>Initial mark/final rewind is performed internally, user do not need to care about it.</li>
+ * <li>Provide strongly typed operators, to guide user on how to use this object.</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * The purpose of this class is to be merged in StorageConnector once its principle has been validated.
*
+ * <em>Guarantees</em>:
* <ul>
- * <li>A {@link java.nio.file.Path} or a {@link java.io.File} for a file or a directory.</li>
- * <li>A {@link java.net.URI} or a {@link java.net.URL} to a distant resource.</li>
- * <li>A {@link CharSequence} interpreted as a filename or a URL.</li>
- * <li>A {@link java.nio.channels.Channel}, {@link DataInput}, {@link InputStream} or {@link Reader}.</li>
- * <li>A {@link DataSource} or a {@link Connection} to a JDBC database.</li>
- * <li>Any other {@code DataStore}-specific object, for example {@link ucar.nc2.NetcdfFile}.</li>
+ * <li>This object is <em>not</em> concurrent, and ensure a <em>fail-fast</em> behavior in such cases.</li>
+ * <li>
+ * useAs* methods will enforce following behavior:
+ * <ul>
+ * <li>If possible, rewind properly consumed storage view to its initial state</li>
+ * <li>If above statement is not possible, an error will be immediately propagated, and the connector will be marked as closed.</li>
+ * </ul>
+ * </li>
* </ul>
*
- * The {@link #getStorageAs(Class)} method provides the storage as an object of the given type, opening
- * the input stream if necessary. This class tries to open the stream only once - subsequent invocation
- * of {@code getStorageAs(…)} may return the same input stream.
+ * Typical usage:
+ * <ol>
+ * <li>Check storage compatibility through `useAs*` methods</li>
+ * <li>If the storage is compatible, commit our choice by locking a storage view, closing the connector in the process.</li>
+ * </ol>
*
- * <p>This class is used only for discovery of a {@code DataStore} implementation capable to handle the input.
- * Once a suitable {@code DataStore} has been found, the {@code StorageConnector} instance is typically
- * discarded since each data store implementation will use their own input/output objects.</p>
+ * Example:
+ * <pre>
+ * final Path file = Paths.get("path/to/file");
+ * try (var c = new StrictStorageConnector(new StorageConnector(file)) {
*
- * <p>Instances of this class are serializable if the {@code storage} object given at construction time
- * is serializable.</p>
+ * // Use connector automatically reset buffering to check support
+ * Boolean isSupported = c.useAsBuffer((buffer) -%gt; buffer.get() == SEARCHED_KEY);
*
- * @author Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since 0.3
- * @module
+ * // Once support is validated, acquire real storage connection. At this point,
+ * // storage life cycle becomes the responsability of the caller, allowing it
+ * // to survive beyond the connector scope.
+ * if (supported) {
+ * try ( InputStream stream = c.commit( InputStream.class ) ) {
+ * // read all needed data from acquired stream
+ * }
+ * }
+ * }
+ * </pre>
*/
-public class StorageConnector implements Serializable {
- /**
- * For cross-version compatibility.
- */
- private static final long serialVersionUID = 2524083964906593093L;
+public class StorageConnector implements AutoCloseable {
- /**
- * The default size of the {@link ByteBuffer} to be created.
- * Users can override this value by providing a value for {@link OptionKey#BYTE_BUFFER}.
- * Note that it usually don't need to be very large, since {@link RewindableLineReader}
- * will have its own buffer (which may be larger) and {@link ChannelDataInput} methods
- * writing in existing destination arrays will bypass the buffer.
- *
- * @see RewindableLineReader#BUFFER_SIZE
- */
- static final int DEFAULT_BUFFER_SIZE = 4096;
-
- /**
- * The minimal size of the {@link ByteBuffer} to be created. This size is used only
- * for temporary buffers that are unlikely to be used for the actual reading process.
- */
- static final int MINIMAL_BUFFER_SIZE = 256;
+ private final UnsafeConnector storage;
- /**
- * A flag for <code>{@linkplain #addView(Class, Object, Class, byte) addView}(…, view, source, flags)</code>
- * telling that after closing the {@code view}, we also need to close the {@code source}.
- * This flag should be set when the view is an {@link ImageInputStream} because Java I/O
- * {@link javax.imageio.stream.FileCacheImageInputStream#close()} does not close the underlying stream.
- * For most other kinds of view, this flag should not be set.
- *
- * @see Coupled#cascadeOnClose()
- */
- private static final byte CASCADE_ON_CLOSE = 1;
+ private WeakReference<Object> committedStorage;
+ private volatile int concurrentFlag;
- /**
- * A flag for <code>{@linkplain #addView(Class, Object, Class, byte) addView}(…, view, source, flags)</code>
- * telling that before reseting the {@code view}, we need to reset the {@code source} first. This flag should
- * can be unset if any change in the position of {@code view} is immediately reflected in the position of
- * {@code source}, and vice-versa.
- *
- * @see Coupled#cascadeOnReset()
- */
- private static final byte CASCADE_ON_RESET = 2;
-
- /**
- * A flag for <code>{@linkplain #addView(Class, Object, Class, byte) addView}(…, view, source, flags)</code>
- * telling that {@code view} can not be reseted, so it should be set to {@code null} instead. This implies
- * that a new view of the same type will be recreated next time it will be requested.
- *
- * <p>When this flag is set, the {@link #CASCADE_ON_RESET} should usually be set in same time.</p>
- */
- private static final byte CLEAR_ON_RESET = 4;
-
- /**
- * Handler to {@code StorageConnector.createFoo()} methods associated to given storage types.
- * Each {@code createFoo()} method may be invoked once for opening an input stream, character
- * reader, database connection, <i>etc</i> from user-supplied path, URI, <i>etc</i>.
- *
- * @param <T> the type of input created by this {@code Opener} instance.
- */
- @FunctionalInterface
- private interface Opener<T> {
- /**
- * Invoked when first needed for creating an input of the requested type.
- * This method should invoke {@link #addView(Class, Object, Class, byte)}
- * for caching the result before to return the view.
- */
- T open(StorageConnector c) throws Exception;
+ public StorageConnector(Object storage) {
+ this(storage instanceof UnsafeConnector ? (UnsafeConnector) storage : new UnsafeConnector(storage));
}
- /** Helper method for {@link #OPENERS} static initialization. */
- private static <T> void add(final Class<T> type, final Opener<T> op) {
- if (OPENERS.put(type, op) != null) throw new AssertionError(type);
+ public StorageConnector(UnsafeConnector storage) {
+ this.storage = storage;
}
- /**
- * List of types recognized by {@link #getStorageAs(Class)}, associated to the methods for opening stream
- * of those types. This map shall contain every types documented in {@link #getStorageAs(Class)} javadoc.
- * {@code null} values means to use {@link ObjectConverters} for that particular type.
- */
- private static final Map<Class<?>, Opener<?>> OPENERS = new IdentityHashMap<>(13);
- static {
- add(String.class, StorageConnector::createString);
- add(ByteBuffer.class, StorageConnector::createByteBuffer);
- add(DataInput.class, StorageConnector::createDataInput);
- add(ImageInputStream.class, StorageConnector::createImageInputStream);
- add(InputStream.class, StorageConnector::createInputStream);
- add(Reader.class, StorageConnector::createReader);
- add(Connection.class, StorageConnector::createConnection);
- add(ChannelDataInput.class, (s) -> s.createChannelDataInput(false)); // Undocumented case (SIS internal)
- add(ChannelFactory.class, (s) -> null); // Undocumented. Shall not cache.
- /*
- * ChannelFactory may have been created as a side effect of creating a ReadableByteChannel.
- * Caller should have asked for another type (e.g. InputStream) before to ask for that type.
- * Consequently null value for ChannelFactory shall not be cached since the actual value may
- * be computed later.
- *
- * Following classes will be converted using ObjectConverters, but without throwing an
- * exception if the conversion fail. Instead, getStorageAs(Class) will return null.
- * Classes not listed here will let the UnconvertibleObjectException propagates.
- */
- add(java.net.URI.class, null);
- add(java.net.URL.class, null);
- add(java.io.File.class, null);
- add(java.nio.file.Path.class, null);
+ public void closeAllExcept(Object view) throws DataStoreException {
+ // Closing multiple times is OK. However, if the view is not null, we will let control raise an error.
+ if (concurrentFlag < 0 && view == null) return;
+ try {
+ doUnderControl(() -> {
+ concurrentFlag = -1;
+ storage.closeAllExcept(view);
+ committedStorage = (view == null ? null : new WeakReference<>(view));
+ return null;
+ });
+ } catch (IOException e) {
+ throw new DataStoreException(e);
+ }
}
/**
- * The input/output object given at construction time.
+ * Provides an in-memory byte buffer containing first bytes of the source storage.
+ * To know how many bytes are available, refer to the buffer {@link ByteBuffer#remaining() remaining byte count}.
+ * User <em>do not</em> need to rewind buffer after use. It is the storage connector responsability.
*
- * @see #getStorage()
+ * @param operator User operation to perform against provided buffer.
+ * @param <T> Type of result produced by user operator.
+ * @return The value computed by input operator.
+ * @throws UnsupportedStorageException If queried storage type cannot be accessed in current context.
+ * @throws IOException If given operator throws IOException on execution.
+ * @throws DataStoreException If an error occurs while fetching queried storage.
*/
- private final Object storage;
-
- /**
- * A name for the input/output object, or {@code null} if none.
- * This field is initialized only when first needed.
- */
- private transient String name;
-
- /**
- * The filename extension, or {@code null} if none.
- * This field is initialized only when first needed.
- */
- private transient String extension;
+ public <T> T useAsBuffer(StorageOperatingFunction<? super ByteBuffer, T> operator) throws DataStoreException, IOException {
+ return use(ByteBuffer.class, new ByteBufferControl(), operator);
+ }
/**
- * The options, created only when first needed.
- *
- * @see #getOption(OptionKey)
- * @see #setOption(OptionKey, Object)
+ * Specialization of {@link #useAs(Class, StorageOperatingFunction)} for {@link ImageInputStream ImageIO API}.
*/
- private Map<OptionKey<?>, Object> options;
+ public <T> T useAsImageInputStream(StorageOperatingFunction<? super ImageInputStream, T> operator) throws IOException, DataStoreException {
+ return use(ImageInputStream.class, new StreamControl<>(ImageInputStream.class, IISAdapter::new), operator);
+ }
/**
- * Views of {@link #storage} as instances of types different than the type of the object given to the constructor.
- * The {@code null} reference can appear in various places:
- *
+ * Temporarily expose storage through queried interface/class to be used by a user defined operator.
+ * Notes:
* <ul>
- * <li>A non-existent entry (equivalent to an entry associated to the {@code null} value) means that the value
- * has not yet been computed.</li>
- * <li>A {@linkplain Coupled#isValid valid entry} with {@link Coupled#view} set to {@code null} means the value
- * has been computed and we have determined that {@link #getStorageAs(Class)} shall return {@code null} for
- * that type.</li>
- * <li>By convention, the {@code null} key is associated to the {@link #storage} value.</li>
+ * <li>
+ * This method handles mark before / rewind after usage. User responsability is to not leave additional
+ * marks unrewinded. Therefore, if you just need to sequentially read input, you don't have to mark/rewind
+ * the storage.
+ * </li>
+ * <li>
+ * Provided storage is checked after use, to ensure it has not been corrupted by input operator. If control
+ * fails, a {@link StorageControlException} is raised.
+ * </li>
* </ul>
*
- * An empty map means that this {@code StorageConnector} has been closed.
- *
- * @see #addView(Class, Object, Class, byte)
- * @see #getView(Class)
- * @see #getStorageAs(Class)
- */
- private transient Map<Class<?>, Coupled> views;
+ * @param storageType Storage access interface to provide to the operator.
+ * @param operator The operator that will access storage to compute a result.
+ * @param <S> Storage class
+ * @param <T> Type of result computed by user operator.
+ * @return The value computed by user operator.
+ * @throws UnsupportedStorageException If queried storage type cannot be accessed in current context.
+ * @throws StorageControlException If connector has detected storage corruption after operator usage.
+ * @throws IOException If given operator throws IOException on execution.
+ * @throws DataStoreException If an error occurs while fetching queried storage.
+ */
+ public <S, T> T useAs(Class<S> storageType, StorageOperatingFunction<? super S, ? extends T> operator) throws IOException, DataStoreException {
+ if (ByteBuffer.class.isAssignableFrom(storageType)) {
+ return useAsBuffer((StorageOperatingFunction<? super ByteBuffer, T>) operator);
+ } else if (ImageInputStream.class.isAssignableFrom(storageType)) {
+ return use(storageType, new StreamControl<>(storageType, stream -> new IISAdapter((ImageInputStream) stream)), operator);
+ } else if (ChannelDataInput.class.isAssignableFrom(storageType)) {
+ return use(storageType, new StreamControl<>(storageType, stream -> new CDIAdapter((ChannelDataInput) stream)), operator);
+ } else if (SeekableByteChannel.class.isAssignableFrom(storageType)) {
+ return use(storageType, new StreamControl<>(storageType, stream -> new SBCAdapter((SeekableByteChannel) stream)), operator);
+ } else if (URI.class.isAssignableFrom(storageType)) {
+ final URI input = getURI().orElseThrow(() -> new UnsupportedStorageException("Cannot acquire an URI"));
+ return ((StorageOperatingFunction<? super URI, T>) operator).apply(input);
+ } else if (Path.class.isAssignableFrom(storageType)) {
+ final Path input = getPath().orElseThrow(() -> new UnsupportedStorageException("Cannot acquire a path"));
+ return ((StorageOperatingFunction<? super Path, T>) operator).apply(input);
+ }
+ else if (File.class.isAssignableFrom(storageType)) {
+ final Path input = getPath().orElseThrow(() -> new UnsupportedStorageException("Cannot acquire a path"));
+ final File inputFile;
+ try {
+ inputFile = input.toFile();
+ } catch (UnsupportedOperationException e) {
+ throw new UnsupportedStorageException("Storage is not a local path (not a file on default file-system).", e);
+ }
+ return ((StorageOperatingFunction<? super File, T>) operator).apply(inputFile);
+ }
+
+ throw new UnsupportedStorageException("Queried storage type is not supported yet: "+storageType);
+ }
+
+ private <I, O> O use(Class<I> storageType, ControlStrategy<I> control, StorageOperatingFunction<? super I, ? extends O> action) throws IOException, DataStoreException {
+ return doUnderControl(() -> {
+ final I rawInput = getOrFail(storageType);
+ final ControlOperator<I> op = control.init(rawInput);
+ try ( Closeable controlAfterUse = op::postControl ) {
+ return action.apply(op.getStorage());
+ } catch (StorageControlException e) {
+ concurrentFlag = -1;
+ throw e;
+ }
+ });
+ }
/**
- * Wraps an instance of @link InputStream}, {@link DataInput}, {@link Reader}, <i>etc.</i> together with additional
- * information about other objects that are coupled with the wrapped object. For example if a {@link Reader} is a
- * wrapper around the user-supplied {@link InputStream}, then those two objects will be wrapped in {@code Coupled}
- * instances together with information about how they are related
+ * Ensure only one storage operation is running at any time against this storage connector. It allows fail-fast
+ * behavior if this connector is used in concurrent context.
*
- * One purpose of {@code Coupled} information is to keep trace of objects which will need to be closed by the
- * {@link StorageConnector#closeAllExcept(Object)} method (for example an {@link InputStreamReader} wrapping
- * an {@link InputStream}).
+ * @param operator The operation to perform once we checked no other operation is running.
*
- * Another purpose is to determine which views need to be synchronized if {@link StorageConnector#storage} is
- * used independently. They are views that may advance {@code storage} position, but not in same time than the
- * {@link #view} position (typically because the view reads some bytes in advance and stores them in a buffer).
- * Such coupling may occur when the storage is an {@link InputStream}, an {@link java.io.OutputStream} or a
- * {@link java.nio.channels.Channel}. The coupled {@link #view} can be:
- *
- * <ul>
- * <li>{@link Reader} that are wrappers around {@code InputStream}.</li>
- * <li>{@link ChannelDataInput} when the channel come from an {@code InputStream}.</li>
- * <li>{@link ChannelDataInput} when the channel has been explicitly given to the constructor.</li>
- * </ul>
+ * @param <V> Type of result value produced by given operator.
+ * @return The result produced by given operator.
+ * @throws IOException If anything wrong happens while input operator consumes storage, or we can mark/rewind storage.
+ * @throws DataStoreException Same reasons as for IOException + can happen if queried storage is of unsupported type.
+ * @throws IllegalStateException If this connector is already closed.
*/
- private static final class Coupled {
- /**
- * The {@link StorageConnector#storage} viewed as another kind of object.
- * Supported types are:
- *
- * <ul>
- * <li>{@link ByteBuffer}:
- * A read-only view of the buffer over the first bytes of the stream.</li>
- *
- * <li>{@link DataInput}:
- * The input as a data input stream. Unless the {@link #storage} is already an instance of {@link DataInput},
- * this entry will be given an instance of {@link ChannelImageInputStream} if possible rather than an arbitrary
- * stream. In particular, we invoke the {@link ImageIO#createImageInputStream(Object)} factory method only in
- * last resort because some SIS data stores will want to access the channel and buffer directly.</li>
- *
- * <li>{@link ImageInputStream}:
- * Same as {@code DataInput} if it can be casted, or {@code null} otherwise.</li>
- *
- * <li>{@link InputStream}:
- * If not explicitly provided, this is a wrapper around the above {@link ImageInputStream}.</li>
- *
- * <li>{@link Reader}:
- * If not explicitly provided, this is a wrapper around the above {@link InputStream}.</li>
- *
- * <li>{@link Connection}:
- * The storage object as a JDBC connection.</li>
- * </ul>
- */
- Object view;
-
- /**
- * The object that {@link #view} is wrapping. For example if {@code view} is an {@link InputStreamReader},
- * then {@code wrapperFor.view} is an {@link InputStream}. This field is {@code null} if {@link #view} ==
- * {@link StorageConnector#storage}.
- */
- final Coupled wrapperFor;
-
- /**
- * The other views that are consuming {@link #view}, or {@code null} if none. For each element in this array,
- * {@code wrappedBy[i].wrapperFor == this}.
- */
- private Coupled[] wrappedBy;
-
- /**
- * Bitwise combination of {@link #CASCADE_ON_CLOSE}, {@link #CASCADE_ON_RESET} or {@link #CLEAR_ON_RESET}.
- */
- final byte cascade;
-
- /**
- * {@code true} if the position of {@link #view} is synchronized with the position of {@link #wrapperFor}.
- */
- boolean isValid;
-
- /**
- * Creates a wrapper for {@link StorageConnector#storage}. This constructor is used when we need to create
- * a {@code Coupled} instance for another view wrapping {@code storage}.
- */
- Coupled(final Object storage) {
- view = storage;
- wrapperFor = null;
- cascade = 0;
- isValid = true;
+ protected <V> V doUnderControl(StorageCallable<? extends V> operator) throws IOException, DataStoreException {
+ if (concurrentFlag < 0) throw new IllegalStateException("...");
+ if (concurrentFlag != 0) throw new ConcurrentReadException("...");
+ concurrentFlag++;
+ try {
+ return operator.call();
+ } finally {
+ concurrentFlag--;
}
+ }
- /**
- * Creates a wrapper for a view wrapping the given {@code Coupled} instance.
- * Caller is responsible to set the {@link #view} field after this constructor call.
- *
- * @param wrapperFor the object that {@link #view} will wrap, or {@code null} if none.
- * @param cascade bitwise combination of {@link #CASCADE_ON_CLOSE}, {@link #CASCADE_ON_RESET}
- * or {@link #CLEAR_ON_RESET}.
- */
- @SuppressWarnings("ThisEscapedInObjectConstruction")
- Coupled(final Coupled wrapperFor, final byte cascade) {
- this.wrapperFor = wrapperFor;
- this.cascade = cascade;
- if (wrapperFor != null) {
- final Coupled[] w = wrapperFor.wrappedBy;
- final int n = (w != null) ? w.length : 0;
- final Coupled[] e = new Coupled[n + 1];
- if (n != 0) System.arraycopy(w, 0, e, 0, n);
- e[n] = this;
- wrapperFor.wrappedBy = e;
- }
- }
+ public Optional<Path> getPath() { return getSilently(Path.class); }
- /**
- * {@code true} if after closing the {@link #view}, we need to also close the {@link #wrapperFor}.
- * Should be {@code true} when the view is an {@link ImageInputStream} because Java I/O
- * {@link javax.imageio.stream.FileCacheImageInputStream#close()} does not close the underlying stream.
- * For most other kinds of view, should be {@code false}.
- */
- final boolean cascadeOnClose() {
- return (cascade & CASCADE_ON_CLOSE) != 0;
- }
+ public Optional<URI> getURI() { return getSilently(URI.class); }
- /**
- * {@code true} if calls to {@link #reset()} should cascade to {@link #wrapperFor}.
- * This is {@code false} if any change in the position of {@link #view} is immediately
- * reflected in the position of {@link #wrapperFor}, and vice-versa.
- */
- final boolean cascadeOnReset() {
- return (cascade & CASCADE_ON_RESET) != 0;
- }
+ public Optional<DataSource> getSQLDatasource() { return getSilently(DataSource.class); }
- /**
- * Declares as invalid all unsynchronized {@code Coupled} instances which are used, directly or indirectly,
- * by this instance. This method is invoked before {@link StorageConnector#getStorageAs(Class)} returns a
- * view, in order to remember which views would need to be resynchronized if they are requested.
- */
- final void invalidateSources() {
- boolean sync = cascadeOnReset();
- for (Coupled c = wrapperFor; sync; c = c.wrapperFor) {
- c.isValid = false;
- sync = c.cascadeOnReset();
- }
- }
+ public Optional<String> getPathAsString() { return getSilently(String.class); }
- /**
- * Declares as invalid all unsynchronized {@code Coupled} instances which are using, directly or indirectly,
- * this instance. This method is invoked before {@link StorageConnector#getStorageAs(Class)} returns a view,
- * in order to remember which views would need to be resynchronized if they are requested.
- */
- final void invalidateUsages() {
- if (wrappedBy != null) {
- for (final Coupled c : wrappedBy) {
- if (c.cascadeOnReset()) {
- c.isValid = false;
- c.invalidateUsages();
- }
- }
- }
+ final boolean prefetch() throws DataStoreException {
+ try {
+ return doUnderControl(() -> storage.prefetch());
+ } catch (IOException e) {
+ throw new DataStoreException("IO error while trying to fetch more content", e);
}
+ }
- /**
- * Identifies the other views to <strong>not</strong> close if we don't want to close the {@link #view}
- * wrapped by this {@code Coupled}. This method identifies only the views that <em>use</em> this view;
- * it does not identify the views <em>used</em> by this view.
- *
- * This method is for {@link StorageConnector#closeAllExcept(Object)} internal usage.
- *
- * @param toClose the map where to write the list of views to not close.
- */
- final void protect(final Map<AutoCloseable,Boolean> toClose) {
- if (wrappedBy != null) {
- for (final Coupled c : wrappedBy) {
- if (!c.cascadeOnClose()) {
- if (c.view instanceof AutoCloseable) {
- toClose.put((AutoCloseable) c.view, Boolean.FALSE);
- }
- c.protect(toClose);
- }
- }
- }
- }
+ /**
+ * Retrieve storage in the queried form, closing all other opened view in the same time.
+ * <em>Warning</em>: This method also closes this storage connector, making invalid any more calls on it.
+ *
+ * @param target Type of the view to get back / keep opened.
+ * @param <T> Type of the wanted storage connection.
+ * @return Underlying storage in the requested form. Never null.
+ *
+ * @throws IOException If anything goes wrong while initializing storage access.
+ * @throws DataStoreException If this connector is used concurrently, or if any problem occurs while initializing view.
+ * @throws IllegalStateException If this connector is already closed.
+ */
+ public <T> T commit(Class<T> target) throws IOException, DataStoreException {
+ return doUnderControl(() -> {
+ final T result = getOrFail(target);
+ concurrentFlag = -1; //close flag
+ storage.closeAllExcept(result);
+ return result;
+ });
+ }
- /**
- * Resets the position of all sources of the {@link #view}, then the view itself.
- *
- * @return {@code true} if some kind of reset has been performed.
- * Note that it does means that the view {@link #isValid} is {@code true}.
- */
- final boolean reset() throws IOException {
- if (isValid) {
- return false;
- }
- /*
- * We need to reset the sources before to reset the view of this Coupled instance.
- * For example if this Coupled instance contains a ChannelDataInput, we need to
- * reset the underlying InputStream before to reset the ChannelDataInput.
- */
- if (cascadeOnReset()) {
- wrapperFor.reset();
- }
- if ((cascade & CLEAR_ON_RESET) != 0) {
- /*
- * If the view can not be reset, in some cases we can discard it and recreate a new view when
- * first needed. The 'isValid' flag is left to false for telling that a new value is requested.
- */
- view = null;
- return true;
- } else if (view instanceof InputStream) {
- /*
- * Note on InputStream.reset() behavior documented in java.io:
- *
- * - It does not discard the mark, so it is okay if reset() is invoked twice.
- * - If mark is unsupported, may either throw IOException or reset the stream
- * to an implementation-dependent fixed state.
- */
- ((InputStream) view).reset();
- } else if (view instanceof Reader) {
- /*
- * Defined as a matter of principle but should not be needed since we do not wrap java.io.Reader
- * (except in BufferedReader if the original storage does not support mark/reset).
- */
- ((Reader) view).reset();
- } else if (view instanceof ChannelDataInput) {
- /*
- * ChannelDataInput can be recycled without the need to discard and recreate them. Note that
- * this code requires that SeekableByteChannel has been seek to the channel beginning first.
- * This should be done by the above 'wrapperFor.reset()' call.
- */
- final ChannelDataInput input = (ChannelDataInput) view;
- input.buffer.limit(0); // Must be after channel reset.
- input.setStreamPosition(0); // Must be after buffer.limit(0).
- } else if (view instanceof Channel) {
- /*
- * Searches for a ChannelDataInput wrapping the channel, because it contains the original position
- * (note: StorageConnector tries to instantiate ChannelDataInput in priority to all other types).
- * If we don't find any, this is considered as a non-seekable channel (we do not assume that the
- * channel original position was 0 when the user gave it to StorageConnector).
- */
- String name = null;
- if (wrappedBy != null) {
- for (Coupled c : wrappedBy) {
- if (c.view instanceof ChannelDataInput) {
- final ChannelDataInput in = ((ChannelDataInput) c.view);
- if (view instanceof SeekableByteChannel) {
- ((SeekableByteChannel) view).position(in.channelOffset);
- return true;
- }
- name = in.filename; // For the error message.
- }
- }
- }
- if (name == null) name = Classes.getShortClassName(view);
- throw new InvalidSeekException(Resources.format(Resources.Keys.StreamIsForwardOnly_1, name));
- } else {
- /*
- * For any other kind of object, we don't know how to recycle them. Current implementation
- * does nothing on the assumption that the object can be reused (example: NetcdfFile).
- */
- }
- isValid = true;
- return true;
- }
+ private <T> T getOrFail(Class<T> target) throws DataStoreException {
+ T view = storage.getStorageAs(target);
+ if (view == null) throw new UnsupportedStorageException();
+ return view;
+ }
- /**
- * Returns a string representation for debugging purpose.
- */
- @Override
- public String toString() {
- return Strings.toString(getClass(),
- "view", Classes.getShortClassName(view),
- "wrapperFor", (wrapperFor != null) ? Classes.getShortClassName(wrapperFor.view) : null,
- "cascade", cascade,
- "isValid", isValid);
+ private <T> Optional<T> getSilently(Class<T> target) {
+ try {
+ return Optional.ofNullable(storage.getStorageAs(target));
+ } catch (UnconvertibleObjectException e) {
+ // TODO: log fine
+ return Optional.empty();
+ } catch (DataStoreException e) {
+ // According to current implementation, that should never happen.
+ // Moreover, it is not really logic to propagate DataStoreException, as this operation should not involve
+ // any "storage" logic (only in-memory path/uri conversion if needed).
+ throw new BackingStoreException(e);
}
+ }
- /**
- * Formats the current {@code Coupled} and all its children as a tree in the given tree table node.
- * This method is used for {@link StorageConnector#toString()} implementation only and may change
- * in any future version.
- *
- * @param appendTo where to write name, value and children.
- * @param views reference to the {@link StorageConnector#views} map. Will be read only.
- */
- @Debug
- final void append(final TreeTable.Node appendTo, final Map<Class<?>, Coupled> views) {
- Class<?> type = null;
- for (final Map.Entry<Class<?>, Coupled> entry : views.entrySet()) {
- if (entry.getValue() == this) {
- final Class<?> t = Classes.findCommonClass(type, entry.getKey());
- if (t != Object.class) type = t;
- }
- }
- appendTo.setValue(TableColumn.NAME, Classes.getShortName(type));
- appendTo.setValue(TableColumn.VALUE, Classes.getShortClassName(view));
- if (wrappedBy != null) {
- for (final Coupled c : wrappedBy) {
- c.append(appendTo.newChild(), views);
- }
- }
- }
+ @Override
+ public void close() throws IOException, DataStoreException {
+ closeAllExcept(committedStorage == null ? null : committedStorage.get());
}
- /**
- * Creates a new data store connection wrapping the given input/output object.
- * The object can be of any type, but the class javadoc lists the most typical ones.
- *
- * @param storage the input/output object as a URL, file, image input stream, <i>etc.</i>.
- */
- public StorageConnector(final Object storage) {
- ArgumentChecks.ensureNonNull("storage", storage);
- this.storage = storage;
+ public <T> T getOption(OptionKey<T> key) {
+ return storage.getOption(key);
}
- /**
- * Returns the option value for the given key, or {@code null} if none.
- *
- * @param <T> the type of option value.
- * @param key the option for which to get the value.
- * @return the current value for the given option, or {@code null} if none.
- */
- public <T> T getOption(final OptionKey<T> key) {
- ArgumentChecks.ensureNonNull("key", key);
- return key.getValueFrom(options);
+ public <T> void setOption(final OptionKey<T> key, final T value) {
+ storage.setOption(key, value);
+ }
+
+ public String getStorageName() {
+ return storage.getStorageName();
+ }
+
+ public String getFileExtension() {
+ return storage.getFileExtension();
}
/**
- * Sets the option value for the given key. The default implementation recognizes the following options:
+ * Please use a safe alternative instead, like {@link #useAs(Class, StorageOperatingFunction)}.
+ * Only call this if safe methods do not support the storage type you require.
*
- * <ul>
- * <li>{@link OptionKey#ENCODING} for decoding characters in an input stream, if needed.</li>
- * <li>{@link OptionKey#URL_ENCODING} for converting URL to URI or filename, if needed.</li>
- * <li>{@link OptionKey#OPEN_OPTIONS} for specifying whether the data store shall be read only or read/write.</li>
- * <li>{@link OptionKey#BYTE_BUFFER} for allowing users to control the byte buffer to be created.</li>
- * </ul>
- *
- * @param <T> the type of option value.
- * @param key the option for which to set the value.
- * @param value the new value for the given option, or {@code null} for removing the value.
+ * @return A connector that does not provides control over resource usage. It does not automatically rewind
+ * automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
*/
- public <T> void setOption(final OptionKey<T> key, final T value) {
- ArgumentChecks.ensureNonNull("key", key);
- options = key.setValueInto(options, value);
+ public <T> T getStorageAs(final Class<T> type) throws IllegalArgumentException, DataStoreException {
+ return storage.getStorageAs(type);
}
/**
- * Returns the input/output object given at construction time.
- * The object can be of any type, but the class javadoc lists the most typical ones.
- *
- * @return the input/output object as a URL, file, image input stream, <i>etc.</i>.
- * @throws DataStoreException if the storage object has already been used and can not be reused.
+ * Please use a safe alternative instead, like {@link #useAs(Class, StorageOperatingFunction)}.
+ * Only call this if safe methods do not support the storage type you require.
*
- * @see #getStorageAs(Class)
+ * @return A connector that does not provides control over resource usage. It does not automatically rewind
+ * automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
*/
+ @Deprecated
public Object getStorage() throws DataStoreException {
- reset();
- return storage;
+ return storage.getStorage();
}
/**
- * Returns a short name of the input/output object. For example if the storage is a file,
- * this method returns the filename without the path (but including the file extension).
- * The default implementation performs the following choices based on the type of the
- * {@linkplain #getStorage() storage} object:
+ * Please try to use {@link #useAs(Class, StorageOperatingFunction)} instead. Only call this if safe methods do not
+ * support the storage type you require.
*
- * <ul>
- * <li>For {@link java.nio.file.Path}, {@link java.io.File}, {@link java.net.URI} or {@link java.net.URL}
- * instances, this method uses dedicated API like {@link java.nio.file.Path#getFileName()}.</li>
- * <li>For {@link CharSequence} instances, this method gets a string representation of the storage object
- * and returns the part after the last {@code '/'} character or platform-dependent name separator.</li>
- * <li>For instances of unknown type, this method builds a string representation using the class name.
- * Note that the string representation of unknown types may change in any future SIS version.</li>
- * </ul>
- *
- * @return a short name of the storage object.
+ * @return A connector that does not provides control over resource usage. It does not automatically rewind
+ * automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
*/
- public String getStorageName() {
- if (name == null) {
- name = IOUtilities.filename(storage);
- if (name == null) {
- name = Classes.getShortClassName(storage);
- }
- }
- return name;
+ public UnsafeConnector unsafe() {
+ return storage;
}
- /**
- * Returns the filename extension of the input/output object. The default implementation performs
- * the following choices based on the type of the {@linkplain #getStorage() storage} object:
- *
- * <ul>
- * <li>For {@link java.nio.file.Path}, {@link java.io.File}, {@link java.net.URI}, {@link java.net.URL} or
- * {@link CharSequence} instances, this method returns the string after the last {@code '.'} character
- * in the filename, provided that the {@code '.'} is not the first filename character. This may be an
- * empty string if the filename has no extension, but never {@code null}.</li>
- * <li>For instances of unknown type, this method returns {@code null}.</li>
- * </ul>
- *
- * @return the filename extension, or an empty string if none,
- * or {@code null} if the storage is an object of unknown type.
- */
- public String getFileExtension() {
- if (extension == null) {
- extension = IOUtilities.extension(storage);
- }
- return extension;
+ @Override
+ public String toString() {
+ return storage.toString();
+ }
+
+ private interface StorageCallable<V> extends Callable<V> {
+ @Override
+ V call() throws IOException, DataStoreException;
+ }
+
+ @FunctionalInterface
+ public interface StorageOperatingFunction<I, O> {
+ O apply(I storage) throws IOException, DataStoreException;
}
/**
- * Returns the storage as a view of the given type if possible, or {@code null} otherwise.
- * The default implementation accepts the following types:
- *
- * <ul>
- * <li>{@link String}:
- * <ul>
- * <li>If the {@linkplain #getStorage() storage} object is an instance of the {@link java.nio.file.Path},
- * {@link java.io.File}, {@link java.net.URL}, {@link java.net.URI} or {@link CharSequence} types,
- * returns the string representation of their path.</li>
- *
- * <li>Otherwise this method returns {@code null}.</li>
- * </ul>
- * </li>
- * <li>{@link java.nio.file.Path}, {@link java.net.URI}, {@link java.net.URL}, {@link java.io.File}:
- * <ul>
- * <li>If the {@linkplain #getStorage() storage} object is an instance of the {@link java.nio.file.Path},
- * {@link java.io.File}, {@link java.net.URL}, {@link java.net.URI} or {@link CharSequence} types and
- * that type can be converted to the requested type, returned the conversion result.</li>
- *
- * <li>Otherwise this method returns {@code null}.</li>
- * </ul>
- * </li>
- * <li>{@link ByteBuffer}:
- * <ul>
- * <li>If the {@linkplain #getStorage() storage} object can be obtained as described in bullet 2 of the
- * {@code DataInput} section below, then this method returns the associated byte buffer.</li>
- *
- * <li>Otherwise this method returns {@code null}.</li>
- * </ul>
- * </li>
- * <li>{@link DataInput}:
- * <ul>
- * <li>If the {@linkplain #getStorage() storage} object is already an instance of {@code DataInput}
- * (including the {@link ImageInputStream} and {@link javax.imageio.stream.ImageOutputStream} types),
- * then it is returned unchanged.</li>
- *
- * <li>Otherwise if the input is an instance of {@link java.nio.file.Path}, {@link java.io.File},
- * {@link java.net.URI}, {@link java.net.URL}, {@link CharSequence}, {@link InputStream} or
- * {@link java.nio.channels.ReadableByteChannel}, then an {@link ImageInputStream} backed by a
- * {@link ByteBuffer} is created when first needed and returned.</li>
- *
- * <li>Otherwise if {@link ImageIO#createImageInputStream(Object)} returns a non-null value,
- * then this value is cached and returned.</li>
- *
- * <li>Otherwise this method returns {@code null}.</li>
- * </ul>
- * </li>
- * <li>{@link ImageInputStream}:
- * <ul>
- * <li>If the above {@code DataInput} can be created and casted to {@code ImageInputStream}, returns it.</li>
- *
- * <li>Otherwise this method returns {@code null}.</li>
- * </ul>
- * </li>
- * <li>{@link InputStream}:
- * <ul>
- * <li>If the {@linkplain #getStorage() storage} object is already an instance of {@link InputStream},
- * then it is returned unchanged.</li>
- *
- * <li>Otherwise if the above {@code ImageInputStream} can be created,
- * returns a wrapper around that stream.</li>
- *
- * <li>Otherwise this method returns {@code null}.</li>
- * </ul>
- * </li>
- * <li>{@link Reader}:
- * <ul>
- * <li>If the {@linkplain #getStorage() storage} object is already an instance of {@link Reader},
- * then it is returned unchanged.</li>
- *
- * <li>Otherwise if the above {@code InputStream} can be created, returns an {@link InputStreamReader}
- * using the encoding specified by {@link OptionKey#ENCODING} if any, or using the system default
- * encoding otherwise.</li>
- *
- * <li>Otherwise this method returns {@code null}.</li>
- * </ul>
- * </li>
- * <li>{@link Connection}:
- * <ul>
- * <li>If the {@linkplain #getStorage() storage} object is already an instance of {@link Connection},
- * then it is returned unchanged.</li>
- *
- * <li>Otherwise if the storage is an instance of {@link DataSource}, then a connection is obtained
- * when first needed and returned.</li>
- *
- * <li>Otherwise this method returns {@code null}.</li>
- * </ul>
- * </li>
- * <li>Any other types:
- * <ul>
- * <li>If the storage given at construction time is already an instance of the requested type,
- * returns it <i>as-is</i>.</li>
- *
- * <li>Otherwise this method throws {@link IllegalArgumentException}.</li>
- * </ul>
- * </li>
- * </ul>
- *
- * Multiple invocations of this method on the same {@code StorageConnector} instance will try
- * to return the same instance on a <cite>best effort</cite> basis. Consequently, implementations of
- * {@link DataStoreProvider#probeContent(StorageConnector)} methods shall not close the stream or
- * database connection returned by this method. In addition, those {@code probeContent(StorageConnector)}
- * methods are responsible for restoring the stream or byte buffer to its original position on return.
- *
- * @param <T> the compile-time type of the {@code type} argument.
- * @param type the desired type as one of {@code ByteBuffer}, {@code DataInput}, {@code Connection}
- * class or other type supported by {@code StorageConnector} subclasses.
- * @return the storage as a view of the given type, or {@code null} if the given type is one of the supported
- * types listed in javadoc but no view can be created for the source given at construction time.
- * @throws IllegalArgumentException if the given {@code type} argument is not one of the supported types
- * listed in this javadoc or in subclass javadoc.
- * @throws IllegalStateException if this {@code StorageConnector} has been {@linkplain #closeAllExcept closed}.
- * @throws DataStoreException if an error occurred while opening a stream or database connection.
- *
- * @see #getStorage()
- * @see #closeAllExcept(Object)
+ * indicates a failure over resource health control (rewind after use, etc.);
*/
- public <T> T getStorageAs(final Class<T> type) throws IllegalArgumentException, DataStoreException {
- ArgumentChecks.ensureNonNull("type", type);
- if (views != null && views.isEmpty()) {
- throw new IllegalStateException(Resources.format(Resources.Keys.ClosedStorageConnector));
- }
- /*
- * Verify if the cache contains an instance created by a previous invocation of this method.
- * Note that InputStream may need to be reseted if it has been used indirectly by other kind
- * of stream (for example a java.io.Reader). Example:
- *
- * 1) The storage specified at construction time is a java.nio.file.Path.
- * 2) getStorageAs(InputStream.class) opens an InputStream. Caller rewinds it after use.
- * 3) getStorageAs(Reader.class) wraps the InputStream. Caller rewinds the Reader after use,
- * but invoking BufferedReader.reset() has no effect on the underlying InputStream.
- * 4) getStorageAs(InputStream.class) needs to rewind the InputStream itself since it was
- * not done at step 3. However doing so invalidate the Reader, so we need to discard it.
- */
- Coupled value = getView(type);
- if (reset(value)) {
- return type.cast(value.view); // null is a valid result.
+ public static class StorageControlException extends RuntimeException {
+ public final Class<?> storageType;
+
+ public StorageControlException(Class<?> storageType) {
+ this(null, null, storageType);
}
- /*
- * If the storage is already an instance of the requested type, returns the storage as-is.
- * We check if the storage needs to be reseted in the same way than in getStorage() method.
- * As a special case, we ensure that InputStream and Reader can be marked.
- */
- if (type.isInstance(storage)) {
- @SuppressWarnings("unchecked")
- T view = (T) storage;
- reset();
- byte cascade = 0;
- if (type == InputStream.class) {
- final InputStream in = (InputStream) view;
- if (!in.markSupported()) {
- view = type.cast(new BufferedInputStream(in));
- cascade = (byte) (CLEAR_ON_RESET | CASCADE_ON_RESET);
- }
- } else if (type == Reader.class) {
- final Reader in = (Reader) view;
- if (!in.markSupported()) {
- view = type.cast(new LineNumberReader(in));
- cascade = (byte) (CLEAR_ON_RESET | CASCADE_ON_RESET);
- }
- }
- addView(type, view, null, cascade);
- return view;
+
+ public StorageControlException(String message, Class<?> storageType) {
+ this(message, null, storageType);
}
- /*
- * If the type is not one of the types listed in OPENERS, we delegate to ObjectConverter.
- * It may throw UnconvertibleObjectException (an IllegalArgumentException subtype) if the
- * given type is unrecognized. So the IllegalArgumentException documented in method javadoc
- * happen (indirectly) here.
- */
- final Opener<?> method = OPENERS.get(type);
- if (method == null) {
- T view;
- try {
- view = ObjectConverters.convert(storage, type);
- } catch (UnconvertibleObjectException e) {
- if (!OPENERS.containsKey(type)) throw e;
- Logging.recoverableException(StoreUtilities.LOGGER, StorageConnector.class, "getStorageAs", e);
- view = null;
- }
- addView(type, view);
- return view;
+
+ public StorageControlException(Throwable cause, Class<?> storageType) {
+ this(null, cause, storageType);
}
- /*
- * No instance has been created previously for the requested type. Open the stream now.
- * Some types will need to reset the InputStream or Channel, but the decision of doing
- * so or not is left to openers. Result will be cached by the 'createFoo()' method.
- * Note that it may cache 'null' value if no stream of the given type can be created.
- */
- final Object view;
- try {
- view = method.open(this);
- } catch (DataStoreException e) {
- throw e;
- } catch (Exception e) {
- short key = Errors.Keys.CanNotOpen_1;
- if (e instanceof NoSuchFileException) {
- key = Errors.Keys.FileNotFound_1;
- }
- throw new DataStoreException(Errors.format(key, getStorageName()), e);
+
+ public StorageControlException(String message, Throwable cause, Class<?> storageType) {
+ super(message, cause);
+ this.storageType = storageType;
}
- return type.cast(view);
}
- /**
- * Resets the given view. If the view is an instance of {@link InputStream}, {@link ReadableByteChannel} or
- * other objects that may be affected by views operations, this method will reset the storage position.
- * The view must have been previously marked by {@link InputStream#mark(int)} or equivalent method.
- *
- * <p>This method is <strong>not</strong> a substitute for the requirement that users leave the
- * {@link #getStorageAs(Class)} return value in the same state as they found it. This method is
- * only for handling the cases where using a view has an indirect impact on another view.</p>
- *
- * <div class="note"><b>Rational:</b>
- * {@link DataStoreProvider#probeContent(StorageConnector)} contract requires that implementers reset the
- * input stream themselves. However if {@link ChannelDataInput} or {@link InputStreamReader} has been used,
- * then the user performed a call to {@link ChannelDataInput#reset()} (for instance), which did not reseted
- * the underlying input stream. So we need to perform the missing {@link InputStream#reset()} here, then
- * synchronize the {@code ChannelDataInput} position accordingly.</div>
- *
- * @param c container of the view to reset, or {@code null} if none.
- * @return {@code true} if the given view, after reset, is valid.
- * Note that {@link Coupled#view} may be null and valid.
- */
- private boolean reset(final Coupled c) throws DataStoreException {
- final boolean done;
- if (c == null) {
- return false;
- } else try {
- done = c.reset();
- } catch (IOException e) {
- throw new ForwardOnlyStorageException(Resources.format(
- Resources.Keys.StreamIsReadOnce_1, getStorageName()), e);
- }
- if (done) {
- c.invalidateSources();
- c.invalidateUsages();
- }
- return c.isValid;
+ interface ControlStrategy<S> {
+ Class<S> getType();
+
+ ControlOperator<S> init(S storage) throws IOException;
}
- /**
- * Resets the root {@link #storage} object.
- *
- * @throws DataStoreException if the storage can not be reseted.
- */
- private void reset() throws DataStoreException {
- if (views != null && !views.isEmpty() && !reset(views.get(null))) {
- throw new ForwardOnlyStorageException(Resources.format(
- Resources.Keys.StreamIsReadOnce_1, getStorageName()));
- }
+ interface ControlOperator<S> {
+ S getStorage();
+ void postControl() throws StorageControlException, IOException;
}
- /**
- * Creates a view for the input as a {@link ChannelDataInput} if possible.
- * This is also a starting point for {@link #createDataInput()} and {@link #createByteBuffer()}.
- * This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.
- *
- * @param asImageInputStream whether the {@code ChannelDataInput} needs to be {@link ChannelImageInputStream} subclass.
- * @throws IOException if an error occurred while opening a channel for the input.
- */
- private ChannelDataInput createChannelDataInput(final boolean asImageInputStream) throws IOException, DataStoreException {
- /*
- * Before to try to wrap an InputStream, mark its position so we can rewind if the user asks for
- * the InputStream directly. We need to reset because ChannelDataInput may have read some bytes.
- * Note that if mark is unsupported, the default InputStream.mark(…) implementation does nothing.
- */
- reset();
- if (storage instanceof InputStream) {
- ((InputStream) storage).mark(DEFAULT_BUFFER_SIZE);
- }
- /*
- * Following method call recognizes ReadableByteChannel, InputStream (with special case for FileInputStream),
- * URL, URI, File, Path or other types that may be added in future Apache SIS versions.
- * If the given storage is already a ReadableByteChannel, then the factory will return it as-is.
- */
- final ChannelFactory factory = ChannelFactory.prepare(storage,
- getOption(OptionKey.URL_ENCODING), false, getOption(OptionKey.OPEN_OPTIONS));
- if (factory == null) {
- return null;
- }
- /*
- * ChannelDataInput depends on ReadableByteChannel, which itself depends on storage
- * (potentially an InputStream). We need to remember this chain in 'Coupled' objects.
- */
- final String name = getStorageName();
- final ReadableByteChannel channel = factory.readable(name, null);
- addView(ReadableByteChannel.class, channel, null, factory.isCoupled() ? CASCADE_ON_RESET : 0);
- ByteBuffer buffer = getOption(OptionKey.BYTE_BUFFER); // User-supplied buffer.
- if (buffer == null) {
- buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); // Default buffer if user did not specified any.
- }
- final ChannelDataInput asDataInput;
- if (asImageInputStream) {
- asDataInput = new ChannelImageInputStream(name, channel, buffer, false);
- } else {
- asDataInput = new ChannelDataInput(name, channel, buffer, false);
+ private static class ByteBufferControl implements ControlStrategy<ByteBuffer> {
+
+ @Override
+ public Class<ByteBuffer> getType() {
+ return ByteBuffer.class;
}
- addView(ChannelDataInput.class, asDataInput, ReadableByteChannel.class, CASCADE_ON_RESET);
- /*
- * Following is an undocumented mechanism for allowing some Apache SIS implementations of DataStore
- * to re-open the same channel or input stream another time, typically for re-reading the same data.
- */
- if (factory.canOpen()) {
- addView(ChannelFactory.class, factory);
+
+ @Override
+ public ControlOperator<ByteBuffer> init(ByteBuffer storage) {
+ final ByteBuffer freezed = storage.asReadOnlyBuffer();
+ return new ControlOperator<ByteBuffer>() {
+ @Override
+ public ByteBuffer getStorage() {
+ return freezed;
+ }
+
+ @Override
+ public void postControl() throws StorageControlException {
+ storage.rewind();
+ }
+ };
}
- return asDataInput;
}
- /**
- * Creates a view for the input as a {@link DataInput} if possible. This method performs the choice
- * documented in the {@link #getStorageAs(Class)} method for the {@code DataInput} case. Opening the
- * data input may imply creating a {@link ByteBuffer}, in which case the buffer will be stored under
- * the {@code ByteBuffer.class} key together with the {@code DataInput.class} case.
- *
- * <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
- *
- * @throws IOException if an error occurred while opening a stream for the input.
- */
- private DataInput createDataInput() throws IOException, DataStoreException {
- /*
- * Gets or creates a ChannelImageInputStream instance if possible. We really need that specific
- * type because some SIS data stores will want to access directly the channel and the buffer.
- * We will fallback on the ImageIO.createImageInputStream(Object) method only in last resort.
- */
- Coupled c = getView(ChannelDataInput.class);
- final ChannelDataInput in;
- if (reset(c)) {
- in = (ChannelDataInput) c.view;
- } else {
- in = createChannelDataInput(true); // May be null.
+ private static class StreamControl<T> implements ControlStrategy<T> {
+
+ private final Class<T> viewType;
+ private final Function<T, ResetableStream> adapter;
+
+ public StreamControl(Class<T> viewType, Function<T, ResetableStream> adapter) {
+ this.viewType = viewType;
+ this.adapter = adapter;
}
- final DataInput asDataInput;
- if (in != null) {
- c = getView(ChannelDataInput.class); // May have been added by createChannelDataInput(…).
- if (in instanceof DataInput) {
- asDataInput = (DataInput) in;
- } else {
- asDataInput = new ChannelImageInputStream(in); // Upgrade existing instance.
- c.view = asDataInput;
- }
- views.put(DataInput.class, c); // Share the same Coupled instance.
- } else {
- reset();
- asDataInput = ImageIO.createImageInputStream(storage);
- addView(DataInput.class, asDataInput, null, (byte) (CASCADE_ON_RESET | CASCADE_ON_CLOSE));
- /*
- * Note: Java Image I/O wrappers for Input/OutputStream do NOT close the underlying streams.
- * This is a complication for us. We could mitigate the problem by subclassing the standard
- * FileCacheImageInputStream and related classes, but we don't do that for now because this
- * code should never be executed for InputStream storage. Instead getChannelDataInput(true)
- * should have created a ChannelImageInputStream or ChannelDataInput.
- */
+
+ @Override
+ public Class<T> getType() {
+ return viewType;
}
- return asDataInput;
- }
- /**
- * Creates a {@link ByteBuffer} from the {@link ChannelDataInput} if possible, or from the
- * {@link ImageInputStream} otherwise. The buffer will be initialized with an arbitrary amount
- * of bytes read from the input. If this amount is not sufficient, it can be increased by a call
- * to {@link #prefetch()}.
- *
- * <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
- *
- * @throws IOException if an error occurred while opening a stream for the input.
- */
- private ByteBuffer createByteBuffer() throws IOException, DataStoreException {
- /*
- * First, try to create the ChannelDataInput if it does not already exists.
- * If successful, this will create a ByteBuffer companion as a side effect.
- */
- final ChannelDataInput c = getStorageAs(ChannelDataInput.class);
- ByteBuffer asByteBuffer = null;
- if (c != null) {
- asByteBuffer = c.buffer.asReadOnlyBuffer();
- } else {
- /*
- * If no ChannelDataInput has been created by the above code, get the input as an ImageInputStream and
- * read an arbitrary amount of bytes. Read only a small amount of bytes because, at the contrary of the
- * buffer created in createChannelDataInput(boolean), the buffer created here is unlikely to be used for
- * the reading process after the recognition of the file format.
- */
- final ImageInputStream in = getStorageAs(ImageInputStream.class);
- if (in != null) {
- in.mark();
- final byte[] buffer = new byte[MINIMAL_BUFFER_SIZE];
- final int n = in.read(buffer);
- in.reset();
- if (n >= 1) {
- // Can not invoke asReadOnly() because 'prefetch()' need to be able to write in it.
- asByteBuffer = ByteBuffer.wrap(buffer).order(in.getByteOrder());
- asByteBuffer.limit(n);
+ @Override
+ public ControlOperator<T> init(T storage) throws IOException {
+ final ResetableStream adaptedStorage = adapter.apply(storage);
+ final long positionCtrl = adaptedStorage.mark();
+
+ return new ControlOperator<T>() {
+ @Override
+ public T getStorage() {
+ return storage;
}
- }
- }
- addView(ByteBuffer.class, asByteBuffer);
- return asByteBuffer;
- }
- /**
- * Transfers more bytes from the {@link DataInput} to the {@link ByteBuffer}, if possible.
- * This method returns {@code true} on success, or {@code false} if input is not a readable
- * channel or stream, we have reached the end of stream, or the buffer is full.
- *
- * <p>This method is invoked when the amount of bytes in the buffer appears to be insufficient
- * for {@link DataStoreProvider#probeContent(StorageConnector)} purpose.</p>
- *
- * @return {@code true} on success.
- * @throws DataStoreException if an error occurred while reading more bytes.
- */
- final boolean prefetch() throws DataStoreException {
- try {
- /*
- * In most Apache SIS data store implementations, we use ChannelDataInput. If the object wrapped
- * by ChannelDataInput has not been used directly, then Coupled.isValid should be true. In such
- * case, reset(c) does nothing and ChannelDataInput.prefetch() will read new bytes from current
- * channel position. Otherwise, a new read operation from the beginning will be required and we
- * can only hope that it will read more bytes than last time.
- */
- Coupled c = getView(ChannelDataInput.class);
- if (c != null) {
- reset(c); // Does nothing is c.isValid is true.
- return c.isValid && ((ChannelDataInput) c.view).prefetch() > 0;
- }
- /*
- * The above code is the usual case. The code below this point is the fallback used when only
- * an ImageInputStream was available. In such case, the ByteBuffer can only be the one created
- * by the above createByteBuffer() method, which is known to be backed by a writable array.
- */
- c = getView(ImageInputStream.class);
- if (reset(c)) {
- final ImageInputStream input = (ImageInputStream) c.view;
- c = getView(ByteBuffer.class);
- if (reset(c)) { // reset(c) as a matter of principle, but (c != null) would have worked.
- final ByteBuffer buffer = (ByteBuffer) c.view;
- final int p = buffer.limit();
- final long mark = input.getStreamPosition();
- input.seek(Math.addExact(mark, p));
- final int n = input.read(buffer.array(), p, buffer.capacity() - p);
- input.seek(mark);
- if (n > 0) {
- buffer.limit(p + n);
- return true;
+ @Override
+ public void postControl() throws StorageControlException, IOException {
+ adaptedStorage.reset();
+
+ final long rewindPosition = adaptedStorage.mark();
+ if (rewindPosition != positionCtrl) {
+ throw new StorageControlException(String.format(
+ "Operator has messed with stream marks. Rewind should have positioned at %d, but ended at %d",
+ positionCtrl, rewindPosition
+ ), viewType);
}
}
- }
- } catch (IOException e) {
- throw new DataStoreException(Errors.format(Errors.Keys.CanNotRead_1, getStorageName()), e);
+ };
}
- return false;
}
- /**
- * Creates an {@link ImageInputStream} from the {@link DataInput} if possible. This method simply
- * casts {@code DataInput} if such cast is allowed. Since {@link #createDataInput()} instantiates
- * {@link ChannelImageInputStream}, this cast is usually possible.
- *
- * <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
- */
- private ImageInputStream createImageInputStream() throws DataStoreException {
- final Class<DataInput> source = DataInput.class;
- final DataInput input = getStorageAs(source);
- if (input instanceof ImageInputStream) {
- views.put(ImageInputStream.class, views.get(source)); // Share the same Coupled instance.
- return (ImageInputStream) input;
- } else {
- addView(ImageInputStream.class, null); // Remember that there is no view.
- return null;
- }
+ private interface ResetableStream {
+ long mark() throws IOException;
+ void reset() throws IOException;
}
- /**
- * Creates an input stream from {@link ReadableByteChannel} if possible, or from {@link ImageInputStream}
- * otherwise.
- *
- * <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
- */
- private InputStream createInputStream() throws IOException, DataStoreException {
- final Class<DataInput> source = DataInput.class;
- final DataInput input = getStorageAs(source);
- if (input instanceof InputStream) {
- views.put(InputStream.class, views.get(source)); // Share the same Coupled instance.
- return (InputStream) input;
- } else if (input instanceof ImageInputStream) {
- /*
- * Wrap the ImageInputStream as an ordinary InputStream. We avoid setting CASCADE_ON_RESET (unless
- * reset() needs to propagate further than ImageInputStream) because changes in InputStreamAdapter
- * position are immediately reflected by corresponding changes in ImageInputStream position.
- */
- final InputStream in = new InputStreamAdapter((ImageInputStream) input);
- addView(InputStream.class, in, source, (byte) (getView(source).cascade & CASCADE_ON_RESET));
- return in;
- } else {
- addView(InputStream.class, null); // Remember that there is no view.
- return null;
+ private static class IISAdapter implements ResetableStream {
+ private final ImageInputStream source;
+
+ IISAdapter(ImageInputStream source) {
+ this.source = source;
}
- }
- /**
- * Creates a character reader if possible.
- *
- * <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
- */
- private Reader createReader() throws IOException, DataStoreException {
- final InputStream input = getStorageAs(InputStream.class);
- if (input == null) {
- addView(Reader.class, null); // Remember that there is no view.
- return null;
+ @Override
+ public long mark() throws IOException {
+ source.mark();
+ return source.getStreamPosition();
}
- input.mark(DEFAULT_BUFFER_SIZE);
- final Reader in = new RewindableLineReader(input, getOption(OptionKey.ENCODING));
- addView(Reader.class, in, InputStream.class, (byte) (CLEAR_ON_RESET | CASCADE_ON_RESET));
- return in;
- }
- /**
- * Creates a database connection if possible.
- *
- * <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
- */
- private Connection createConnection() throws SQLException {
- if (storage instanceof DataSource) {
- final Connection c = ((DataSource) storage).getConnection();
- addView(Connection.class, c, null, (byte) 0);
- return c;
+ @Override
+ public void reset() throws IOException {
+ source.reset();
}
- return null;
}
- /**
- * Returns the storage as a path if possible, or {@code null} otherwise.
- *
- * <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
- */
- private String createString() {
- return IOUtilities.toString(storage);
- }
+ private static class CDIAdapter implements ResetableStream {
+ private final ChannelDataInput source;
- /**
- * Adds the given view in the cache, without dependencies.
- *
- * @param <T> the compile-time type of the {@code type} argument.
- * @param type the view type.
- * @param view the view, or {@code null} if none.
- */
- private <T> void addView(final Class<T> type, final T view) {
- addView(type, view, null, (byte) 0);
- }
+ CDIAdapter(ChannelDataInput source) {
+ this.source = source;
+ }
- /**
- * Adds the given view in the cache together with information about its dependency.
- * For example {@link InputStreamReader} is a wrapper for a {@link InputStream}: read operations
- * from the later may change position of the former, and closing the later also close the former.
- *
- * @param <T> the compile-time type of the {@code type} argument.
- * @param type the view type.
- * @param view the view, or {@code null} if none.
- * @param source the type of input that {@code view} is wrapping, or {@code null} for {@link #storage}.
- * @param cascade bitwise combination of {@link #CASCADE_ON_CLOSE}, {@link #CASCADE_ON_RESET} or {@link #CLEAR_ON_RESET}.
- */
- private <T> void addView(final Class<T> type, final T view, final Class<?> source, final byte cascade) {
- if (views == null) {
- views = new IdentityHashMap<>();
- views.put(null, new Coupled(storage));
+ @Override
+ public long mark() throws IOException {
+ source.mark();
+ return source.getStreamPosition();
}
- Coupled c = views.get(type);
- if (c == null) {
- if (view == storage) {
- c = views.get(null);
- c.invalidateUsages();
- } else {
- c = new Coupled(cascade != 0 ? views.get(source) : null, cascade);
- // Newly created objects are not yet used by anyone, so no need to invoke c.invalidateUsages().
- }
- views.put(type, c);
- } else {
- assert c.view == null || c.view == view : c;
- assert c.cascade == cascade : cascade;
- assert c.wrapperFor == (cascade != 0 ? views.get(source) : null) : c;
- c.invalidateUsages();
+
+ @Override
+ public void reset() throws IOException {
+ source.reset();
}
- c.view = view;
- c.isValid = true;
- c.invalidateSources();
}
- /**
- * Returns the view for the given type from the cache.
- * This method does <strong>not</strong> {@linkplain #reset(Coupled) reset} the view.
- *
- * @param type the view type, or {@code null} for the {@link #storage} container.
- * @return information associated to the given type. May be {@code null} if the view has never been
- * requested before. {@link Coupled#view} may be {@code null} if the view has been requested
- * and we determined that none can be created.
- */
- private Coupled getView(final Class<?> type) {
- return (views != null) ? views.get(type) : null;
- }
+ private static class SBCAdapter implements ResetableStream {
+ final SeekableByteChannel source;
- /**
- * Closes all streams and connections created by this {@code StorageConnector} except the given view.
- * This method closes all objects created by the {@link #getStorageAs(Class)} method except the given {@code view}.
- * If {@code view} is {@code null}, then this method closes everything including the {@linkplain #getStorage()
- * storage} if it is closeable.
- *
- * <p>This method is invoked when a suitable {@link DataStore} has been found - in which case the view used
- * by the data store is given in argument to this method - or when no suitable {@code DataStore} has been
- * found - in which case the {@code view} argument is null.</p>
- *
- * <p>This {@code StorageConnector} instance shall not be used anymore after invocation of this method.</p>
- *
- * @param view the view to leave open, or {@code null} if none.
- * @throws DataStoreException if an error occurred while closing the stream or database connection.
- *
- * @see #getStorageAs(Class)
- * @see DataStoreProvider#open(StorageConnector)
- */
- public void closeAllExcept(final Object view) throws DataStoreException {
- if (views == null) {
- views = Collections.emptyMap(); // For blocking future usage of this StorageConnector instance.
- if (storage != view && storage instanceof AutoCloseable) try {
- ((AutoCloseable) storage).close();
- } catch (DataStoreException e) {
- throw e;
- } catch (Exception e) {
- throw new DataStoreException(e);
- }
- return;
- }
- /*
- * Create a list of all views to close. The boolean value is TRUE if the view should be closed, or FALSE
- * if the view should be protected (not closed). FALSE values shall have precedence over TRUE values.
- */
- final Map<AutoCloseable,Boolean> toClose = new IdentityHashMap<>(views.size());
- for (Coupled c : views.values()) {
- @SuppressWarnings("null")
- Object v = c.view;
- if (v != view) {
- if (v instanceof AutoCloseable) {
- toClose.putIfAbsent((AutoCloseable) v, Boolean.TRUE); // Mark 'v' as needing to be closed.
- }
- } else {
- /*
- * If there is a view to not close, search for all views that are wrapper for the given view.
- * Those wrappers shall not be closed. For example if the caller does not want to close the
- * InputStream view, then we shall not close the InputStreamReader wrapper neither.
- */
- c.protect(toClose);
- do {
- v = c.view;
- if (v instanceof AutoCloseable) {
- toClose.put((AutoCloseable) v, Boolean.FALSE); // Protect 'v' against closing.
- }
- c = c.wrapperFor;
- } while (c != null);
- }
- }
- /*
- * Trim the map in order to keep only the views to close.
- */
- for (final Iterator<Boolean> it = toClose.values().iterator(); it.hasNext();) {
- if (Boolean.FALSE.equals(it.next())) {
- it.remove();
- }
- }
- /*
- * The "AutoCloseable.close() is not indempotent" problem
- * ------------------------------------------------------
- * We will need a set of objects to close without duplicated values. For example the values associated to the
- * 'ImageInputStream.class' and 'DataInput.class' keys are often the same instance. We must avoid duplicated
- * values because 'ImageInputStream.close()' is not indempotent, i.e. invoking their 'close()' method twice
- * will thrown an IOException.
- *
- * Generally speaking, all AutoCloseable instances are not guaranteed to be indempotent because this is not
- * required by the interface contract. Consequently we must be careful to not invoke the close() method on
- * the same instance twice (indirectly or indirectly). An exception to this rule is ImageInputStream, which
- * does not close its underlying stream. Those exceptions are identified by 'cascadeOnClose' set to 'true'.
- */
- if (!toClose.isEmpty()) {
- for (Coupled c : views.values()) {
- if (!c.cascadeOnClose() && toClose.containsKey(c.view)) { // Keep (do not remove) the "top level" view.
- while ((c = c.wrapperFor) != null) {
- toClose.remove(c.view); // Remove all views below the "top level" one.
- if (c.cascadeOnClose()) break;
- }
- }
- }
- }
- views = Collections.emptyMap(); // For blocking future usage of this StorageConnector instance.
- /*
- * Now close all remaining items. Typically (but not necessarily) there is only one remaining item.
- * If an exception occurs, we will propagate it only after we are done closing all items.
- */
- DataStoreException failure = null;
- for (final AutoCloseable c : toClose.keySet()) {
- try {
- c.close();
- } catch (Exception e) {
- if (failure == null) {
- failure = (e instanceof DataStoreException) ? (DataStoreException) e : new DataStoreException(e);
- } else {
- failure.addSuppressed(e);
- }
- }
- }
- if (failure != null) {
- throw failure;
+ long mark = -1;
+
+ SBCAdapter(SeekableByteChannel source) {
+ this.source = source;
}
- }
- /**
- * Returns a string representation of this {@code StorageConnector} for debugging purpose.
- * This string representation is for debugging purpose only and may change in any future version.
- *
- * @return a string representation of this {@code StorageConnector} for debugging purpose.
- */
- @Override
- public String toString() {
- final TreeTable table = new DefaultTreeTable(TableColumn.NAME, TableColumn.VALUE);
- final TreeTable.Node root = table.getRoot();
- root.setValue(TableColumn.NAME, Classes.getShortClassName(this));
- root.setValue(TableColumn.VALUE, getStorageName());
- if (options != null) {
- final TreeTable.Node op = root.newChild();
- op.setValue(TableColumn.NAME, "options");
- op.setValue(TableColumn.VALUE, options);
+ @Override
+ public long mark() throws IOException {
+ final long position = source.position();
+ if (mark >= 0 && position != mark) throw new IllegalStateException("This stream is already marked, cannot add one.");
+ this.mark = position;
+ return mark;
}
- final Coupled c = getView(null);
- if (c != null) {
- c.append(root.newChild(), views);
+
+ @Override
+ public void reset() throws IOException {
+ if (mark >= 0) source.position(mark);
}
- return table.toString();
}
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/StrictStorageConnector.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/StrictStorageConnector.java
deleted file mode 100644
index 23934cc..0000000
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/StrictStorageConnector.java
+++ /dev/null
@@ -1,497 +0,0 @@
-package org.apache.sis.storage;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.concurrent.Callable;
-import java.util.function.Function;
-import javax.imageio.stream.ImageInputStream;
-import javax.sql.DataSource;
-import org.apache.sis.internal.storage.io.ChannelDataInput;
-import org.apache.sis.setup.OptionKey;
-import org.apache.sis.util.UnconvertibleObjectException;
-import org.apache.sis.util.collection.BackingStoreException;
-
-/**
- * Extension of a storage connector providing strong encapsulation of "views". This allows to:
- * <ul>
- * <li>Adopt a <em>fail-fast</em> behavior in case storage view is corrupted by a user</li>
- * <li>
- * Provide easier usage:
- * <ul>
- * <li>Initial mark/final rewind is performed internally, user do not need to care about it.</li>
- * <li>Provide strongly typed operators, to guide user on how to use this object.</li>
- * </ul>
- * </li>
- * </ul>
- * The purpose of this class is to be merged in StorageConnector once its principle has been validated.
- *
- * <em>Guarantees</em>:
- * <ul>
- * <li>This object is <em>not</em> concurrent, and ensure a <em>fail-fast</em> behavior in such cases.</li>
- * <li>
- * useAs* methods will enforce following behavior:
- * <ul>
- * <li>If possible, rewind properly consumed storage view to its initial state</li>
- * <li>If above statement is not possible, an error will be immediately propagated, and the connector will be marked as closed.</li>
- * </ul>
- * </li>
- * </ul>
- *
- * Typical usage:
- * <ol>
- * <li>Check storage compatibility through `useAs*` methods</li>
- * <li>If the storage is compatible, commit our choice by locking a storage view, closing the connector in the process.</li>
- * </ol>
- *
- * Example:
- * <pre>
- * final Path file = Paths.get("path/to/file");
- * try (var c = new StrictStorageConnector(new StorageConnector(file)) {
- *
- * // Use connector automatically reset buffering to check support
- * Boolean isSupported = c.useAsBuffer((buffer) -%gt; buffer.get() == SEARCHED_KEY);
- *
- * // Once support is validated, acquire real storage connection. At this point,
- * // storage life cycle becomes the responsability of the caller, allowing it
- * // to survive beyond the connector scope.
- * if (supported) {
- * try ( InputStream stream = c.commit( InputStream.class ) ) {
- * // read all needed data from acquired stream
- * }
- * }
- * }
- * </pre>
- */
-public class StrictStorageConnector implements AutoCloseable {
-
- private final StorageConnector storage;
-
- private WeakReference<Object> committedStorage;
- private volatile int concurrentFlag;
-
- public StrictStorageConnector(StorageConnector storage) {
- this.storage = storage;
- }
-
- public void closeAllExcept(Object view) throws DataStoreException {
- // Closing multiple times is OK. However, if the view is not null, we will let control raise an error.
- if (concurrentFlag < 0 && view == null) return;
- try {
- doUnderControl(() -> {
- concurrentFlag = -1;
- storage.closeAllExcept(view);
- committedStorage = (view == null ? null : new WeakReference<>(view));
- return null;
- });
- } catch (IOException e) {
- throw new DataStoreException(e);
- }
- }
-
- /**
- * Provides an in-memory byte buffer containing first bytes of the source storage.
- * To know how many bytes are available, refer to the buffer {@link ByteBuffer#remaining() remaining byte count}.
- * User <em>do not</em> need to rewind buffer after use. It is the storage connector responsability.
- *
- * @param operator User operation to perform against provided buffer.
- * @param <T> Type of result produced by user operator.
- * @return The value computed by input operator.
- * @throws UnsupportedStorageException If queried storage type cannot be accessed in current context.
- * @throws IOException If given operator throws IOException on execution.
- * @throws DataStoreException If an error occurs while fetching queried storage.
- */
- public <T> T useAsBuffer(StorageOperatingFunction<? super ByteBuffer, T> operator) throws DataStoreException, IOException {
- return use(ByteBuffer.class, new ByteBufferControl(), operator);
- }
-
- /**
- * Specialization of {@link #useAs(Class, StorageOperatingFunction)} for {@link ImageInputStream ImageIO API}.
- */
- public <T> T useAsImageInputStream(StorageOperatingFunction<? super ImageInputStream, T> operator) throws IOException, DataStoreException {
- return use(ImageInputStream.class, new StreamControl<>(ImageInputStream.class, IISAdapter::new), operator);
- }
-
- /**
- * Temporarily expose storage through queried interface/class to be used by a user defined operator.
- * Notes:
- * <ul>
- * <li>
- * This method handles mark before / rewind after usage. User responsability is to not leave additional
- * marks unrewinded. Therefore, if you just need to sequentially read input, you don't have to mark/rewind
- * the storage.
- * </li>
- * <li>
- * Provided storage is checked after use, to ensure it has not been corrupted by input operator. If control
- * fails, a {@link StorageControlException} is raised.
- * </li>
- * </ul>
- *
- * @param storageType Storage access interface to provide to the operator.
- * @param operator The operator that will access storage to compute a result.
- * @param <S> Storage class
- * @param <T> Type of result computed by user operator.
- * @return The value computed by user operator.
- * @throws UnsupportedStorageException If queried storage type cannot be accessed in current context.
- * @throws StorageControlException If connector has detected storage corruption after operator usage.
- * @throws IOException If given operator throws IOException on execution.
- * @throws DataStoreException If an error occurs while fetching queried storage.
- */
- public <S, T> T useAs(Class<S> storageType, StorageOperatingFunction<? super S, ? extends T> operator) throws IOException, DataStoreException {
- if (ByteBuffer.class.isAssignableFrom(storageType)) {
- return useAsBuffer((StorageOperatingFunction<? super ByteBuffer, T>) operator);
- } else if (ImageInputStream.class.isAssignableFrom(storageType)) {
- return use(storageType, new StreamControl<>(storageType, stream -> new IISAdapter((ImageInputStream) stream)), operator);
- } else if (ChannelDataInput.class.isAssignableFrom(storageType)) {
- return use(storageType, new StreamControl<>(storageType, stream -> new CDIAdapter((ChannelDataInput) stream)), operator);
- } else if (SeekableByteChannel.class.isAssignableFrom(storageType)) {
- return use(storageType, new StreamControl<>(storageType, stream -> new SBCAdapter((SeekableByteChannel) stream)), operator);
- } else if (URI.class.isAssignableFrom(storageType)) {
- final URI input = getURI().orElseThrow(() -> new UnsupportedStorageException("Cannot acquire an URI"));
- return ((StorageOperatingFunction<? super URI, T>) operator).apply(input);
- } else if (Path.class.isAssignableFrom(storageType)) {
- final Path input = getPath().orElseThrow(() -> new UnsupportedStorageException("Cannot acquire a path"));
- return ((StorageOperatingFunction<? super Path, T>) operator).apply(input);
- }
- else if (File.class.isAssignableFrom(storageType)) {
- final Path input = getPath().orElseThrow(() -> new UnsupportedStorageException("Cannot acquire a path"));
- final File inputFile;
- try {
- inputFile = input.toFile();
- } catch (UnsupportedOperationException e) {
- throw new UnsupportedStorageException("Storage is not a local path (not a file on default file-system).", e);
- }
- return ((StorageOperatingFunction<? super File, T>) operator).apply(inputFile);
- }
-
- throw new UnsupportedStorageException("Queried storage type is not supported yet: "+storageType);
- }
-
- private <I, O> O use(Class<I> storageType, ControlStrategy<I> control, StorageOperatingFunction<? super I, ? extends O> action) throws IOException, DataStoreException {
- return doUnderControl(() -> {
- final I rawInput = getOrFail(storageType);
- final ControlOperator<I> op = control.init(rawInput);
- try ( Closeable controlAfterUse = op::postControl ) {
- return action.apply(op.getStorage());
- } catch (StorageControlException e) {
- concurrentFlag = -1;
- throw e;
- }
- });
- }
-
- /**
- * Ensure only one storage operation is running at any time against this storage connector. It allows fail-fast
- * behavior if this connector is used in concurrent context.
- *
- * @param operator The operation to perform once we checked no other operation is running.
- *
- * @param <V> Type of result value produced by given operator.
- * @return The result produced by given operator.
- * @throws IOException If anything wrong happens while input operator consumes storage, or we can mark/rewind storage.
- * @throws DataStoreException Same reasons as for IOException + can happen if queried storage is of unsupported type.
- * @throws IllegalStateException If this connector is already closed.
- */
- protected <V> V doUnderControl(StorageCallable<? extends V> operator) throws IOException, DataStoreException {
- if (concurrentFlag < 0) throw new IllegalStateException("...");
- if (concurrentFlag != 0) throw new ConcurrentReadException("...");
- concurrentFlag++;
- try {
- return operator.call();
- } finally {
- concurrentFlag--;
- }
- }
-
- public Optional<Path> getPath() { return getSilently(Path.class); }
-
- public Optional<URI> getURI() { return getSilently(URI.class); }
-
- public Optional<DataSource> getSQLDatasource() { return getSilently(DataSource.class); }
-
- public Optional<String> getPathAsString() { return getSilently(String.class); }
-
- /**
- * Retrieve storage in the queried form, closing all other opened view in the same time.
- * <em>Warning</em>: This method also closes this storage connector, making invalid any more calls on it.
- *
- * @param target Type of the view to get back / keep opened.
- * @param <T> Type of the wanted storage connection.
- * @return Underlying storage in the requested form. Never null.
- *
- * @throws IOException If anything goes wrong while initializing storage access.
- * @throws DataStoreException If this connector is used concurrently, or if any problem occurs while initializing view.
- * @throws IllegalStateException If this connector is already closed.
- */
- public <T> T commit(Class<T> target) throws IOException, DataStoreException {
- return doUnderControl(() -> {
- final T result = getOrFail(target);
- concurrentFlag = -1; //close flag
- storage.closeAllExcept(result);
- return result;
- });
- }
-
- private <T> T getOrFail(Class<T> target) throws DataStoreException {
- T view = storage.getStorageAs(target);
- if (view == null) throw new UnsupportedStorageException();
- return view;
- }
-
- private <T> Optional<T> getSilently(Class<T> target) {
- try {
- return Optional.ofNullable(storage.getStorageAs(target));
- } catch (UnconvertibleObjectException e) {
- // TODO: log fine
- return Optional.empty();
- } catch (DataStoreException e) {
- // According to current implementation, that should never happen.
- // Moreover, it is not really logic to propagate DataStoreException, as this operation should not involve
- // any "storage" logic (only in-memory path/uri conversion if needed).
- throw new BackingStoreException(e);
- }
- }
-
- @Override
- public void close() throws IOException, DataStoreException {
- closeAllExcept(committedStorage == null ? null : committedStorage.get());
- }
-
- public <T> T getOption(OptionKey<T> key) {
- return storage.getOption(key);
- }
-
- public <T> void setOption(final OptionKey<T> key, final T value) {
- storage.setOption(key, value);
- }
-
- public String getStorageName() {
- return storage.getStorageName();
- }
-
- public String getFileExtension() {
- return storage.getFileExtension();
- }
-
- /**
- * Please use a safe alternative instead, like {@link #useAs(Class, StorageOperatingFunction)}.
- * Only call this if safe methods do not support the storage type you require.
- *
- * @return A connector that does not provides control over resource usage. It does not automatically rewind
- * automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
- */
- public <T> T getStorageAs(final Class<T> type) throws IllegalArgumentException, DataStoreException {
- return storage.getStorageAs(type);
- }
-
- /**
- * Please use a safe alternative instead, like {@link #useAs(Class, StorageOperatingFunction)}.
- * Only call this if safe methods do not support the storage type you require.
- *
- * @return A connector that does not provides control over resource usage. It does not automatically rewind
- * automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
- */
- @Deprecated
- public Object getStorage() throws DataStoreException {
- return storage.getStorage();
- }
-
- /**
- * Please try to use {@link #useAs(Class, StorageOperatingFunction)} instead. Only call this if safe methods do not
- * support the storage type you require.
- *
- * @return A connector that does not provides control over resource usage. It does not automatically rewind
- * automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
- */
- public StorageConnector unsafe() {
- return storage;
- }
-
- @Override
- public String toString() {
- return storage.toString();
- }
-
- private interface StorageCallable<V> extends Callable<V> {
- @Override
- V call() throws IOException, DataStoreException;
- }
-
- @FunctionalInterface
- public interface StorageOperatingFunction<I, O> {
- O apply(I storage) throws IOException, DataStoreException;
- }
-
- /**
- * indicates a failure over resource health control (rewind after use, etc.);
- */
- public static class StorageControlException extends RuntimeException {
- public final Class<?> storageType;
-
- public StorageControlException(Class<?> storageType) {
- this(null, null, storageType);
- }
-
- public StorageControlException(String message, Class<?> storageType) {
- this(message, null, storageType);
- }
-
- public StorageControlException(Throwable cause, Class<?> storageType) {
- this(null, cause, storageType);
- }
-
- public StorageControlException(String message, Throwable cause, Class<?> storageType) {
- super(message, cause);
- this.storageType = storageType;
- }
- }
-
- interface ControlStrategy<S> {
- Class<S> getType();
-
- ControlOperator<S> init(S storage) throws IOException;
- }
-
- interface ControlOperator<S> {
- S getStorage();
- void postControl() throws StorageControlException, IOException;
- }
-
- private static class ByteBufferControl implements ControlStrategy<ByteBuffer> {
-
- @Override
- public Class<ByteBuffer> getType() {
- return ByteBuffer.class;
- }
-
- @Override
- public ControlOperator<ByteBuffer> init(ByteBuffer storage) {
- final ByteBuffer freezed = storage.asReadOnlyBuffer();
- return new ControlOperator<ByteBuffer>() {
- @Override
- public ByteBuffer getStorage() {
- return freezed;
- }
-
- @Override
- public void postControl() throws StorageControlException {
- storage.rewind();
- }
- };
- }
- }
-
- private static class StreamControl<T> implements ControlStrategy<T> {
-
- private final Class<T> viewType;
- private final Function<T, ResetableStream> adapter;
-
- public StreamControl(Class<T> viewType, Function<T, ResetableStream> adapter) {
- this.viewType = viewType;
- this.adapter = adapter;
- }
-
- @Override
- public Class<T> getType() {
- return viewType;
- }
-
- @Override
- public ControlOperator<T> init(T storage) throws IOException {
- final ResetableStream adaptedStorage = adapter.apply(storage);
- final long positionCtrl = adaptedStorage.mark();
-
- return new ControlOperator<T>() {
- @Override
- public T getStorage() {
- return storage;
- }
-
- @Override
- public void postControl() throws StorageControlException, IOException {
- adaptedStorage.reset();
-
- final long rewindPosition = adaptedStorage.mark();
- if (rewindPosition != positionCtrl) {
- throw new StorageControlException(String.format(
- "Operator has messed with stream marks. Rewind should have positioned at %d, but ended at %d",
- positionCtrl, rewindPosition
- ), viewType);
- }
- }
- };
- }
- }
-
- private interface ResetableStream {
- long mark() throws IOException;
- void reset() throws IOException;
- }
-
- private static class IISAdapter implements ResetableStream {
- private final ImageInputStream source;
-
- IISAdapter(ImageInputStream source) {
- this.source = source;
- }
-
- @Override
- public long mark() throws IOException {
- source.mark();
- return source.getStreamPosition();
- }
-
- @Override
- public void reset() throws IOException {
- source.reset();
- }
- }
-
- private static class CDIAdapter implements ResetableStream {
- private final ChannelDataInput source;
-
- CDIAdapter(ChannelDataInput source) {
- this.source = source;
- }
-
- @Override
- public long mark() throws IOException {
- source.mark();
- return source.getStreamPosition();
- }
-
- @Override
- public void reset() throws IOException {
- source.reset();
- }
- }
-
- private static class SBCAdapter implements ResetableStream {
- final SeekableByteChannel source;
-
- long mark = -1;
-
- SBCAdapter(SeekableByteChannel source) {
- this.source = source;
- }
-
- @Override
- public long mark() throws IOException {
- final long position = source.position();
- if (mark >= 0 && position != mark) throw new IllegalStateException("This stream is already marked, cannot add one.");
- this.mark = position;
- return mark;
- }
-
- @Override
- public void reset() throws IOException {
- if (mark >= 0) source.position(mark);
- }
- }
-}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/UnsafeConnector.java
similarity index 95%
copy from storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
copy to storage/sis-storage/src/main/java/org/apache/sis/storage/UnsafeConnector.java
index 90bae45..fbee6e1 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/UnsafeConnector.java
@@ -63,7 +63,7 @@ import org.apache.sis.setup.OptionKey;
/**
* Information for creating a connection to a {@link DataStore} in read and/or write mode.
- * {@code StorageConnector} wraps an input {@link Object}, which can be any of the following types:
+ * {@code UnsafeConnector} wraps an input {@link Object}, which can be any of the following types:
*
* <ul>
* <li>A {@link java.nio.file.Path} or a {@link java.io.File} for a file or a directory.</li>
@@ -79,7 +79,7 @@ import org.apache.sis.setup.OptionKey;
* of {@code getStorageAs(…)} may return the same input stream.
*
* <p>This class is used only for discovery of a {@code DataStore} implementation capable to handle the input.
- * Once a suitable {@code DataStore} has been found, the {@code StorageConnector} instance is typically
+ * Once a suitable {@code DataStore} has been found, the {@code UnsafeConnector} instance is typically
* discarded since each data store implementation will use their own input/output objects.</p>
*
* <p>Instances of this class are serializable if the {@code storage} object given at construction time
@@ -90,7 +90,7 @@ import org.apache.sis.setup.OptionKey;
* @since 0.3
* @module
*/
-public class StorageConnector implements Serializable {
+public class UnsafeConnector implements Serializable {
/**
* For cross-version compatibility.
*/
@@ -144,7 +144,7 @@ public class StorageConnector implements Serializable {
private static final byte CLEAR_ON_RESET = 4;
/**
- * Handler to {@code StorageConnector.createFoo()} methods associated to given storage types.
+ * Handler to {@code UnsafeConnector.createFoo()} methods associated to given storage types.
* Each {@code createFoo()} method may be invoked once for opening an input stream, character
* reader, database connection, <i>etc</i> from user-supplied path, URI, <i>etc</i>.
*
@@ -157,7 +157,7 @@ public class StorageConnector implements Serializable {
* This method should invoke {@link #addView(Class, Object, Class, byte)}
* for caching the result before to return the view.
*/
- T open(StorageConnector c) throws Exception;
+ T open(UnsafeConnector c) throws Exception;
}
/** Helper method for {@link #OPENERS} static initialization. */
@@ -172,13 +172,13 @@ public class StorageConnector implements Serializable {
*/
private static final Map<Class<?>, Opener<?>> OPENERS = new IdentityHashMap<>(13);
static {
- add(String.class, StorageConnector::createString);
- add(ByteBuffer.class, StorageConnector::createByteBuffer);
- add(DataInput.class, StorageConnector::createDataInput);
- add(ImageInputStream.class, StorageConnector::createImageInputStream);
- add(InputStream.class, StorageConnector::createInputStream);
- add(Reader.class, StorageConnector::createReader);
- add(Connection.class, StorageConnector::createConnection);
+ add(String.class, UnsafeConnector::createString);
+ add(ByteBuffer.class, UnsafeConnector::createByteBuffer);
+ add(DataInput.class, UnsafeConnector::createDataInput);
+ add(ImageInputStream.class, UnsafeConnector::createImageInputStream);
+ add(InputStream.class, UnsafeConnector::createInputStream);
+ add(Reader.class, UnsafeConnector::createReader);
+ add(Connection.class, UnsafeConnector::createConnection);
add(ChannelDataInput.class, (s) -> s.createChannelDataInput(false)); // Undocumented case (SIS internal)
add(ChannelFactory.class, (s) -> null); // Undocumented. Shall not cache.
/*
@@ -237,7 +237,7 @@ public class StorageConnector implements Serializable {
* <li>By convention, the {@code null} key is associated to the {@link #storage} value.</li>
* </ul>
*
- * An empty map means that this {@code StorageConnector} has been closed.
+ * An empty map means that this {@code UnsafeConnector} has been closed.
*
* @see #addView(Class, Object, Class, byte)
* @see #getView(Class)
@@ -252,10 +252,10 @@ public class StorageConnector implements Serializable {
* instances together with information about how they are related
*
* One purpose of {@code Coupled} information is to keep trace of objects which will need to be closed by the
- * {@link StorageConnector#closeAllExcept(Object)} method (for example an {@link InputStreamReader} wrapping
+ * {@link UnsafeConnector#closeAllExcept(Object)} method (for example an {@link InputStreamReader} wrapping
* an {@link InputStream}).
*
- * Another purpose is to determine which views need to be synchronized if {@link StorageConnector#storage} is
+ * Another purpose is to determine which views need to be synchronized if {@link UnsafeConnector#storage} is
* used independently. They are views that may advance {@code storage} position, but not in same time than the
* {@link #view} position (typically because the view reads some bytes in advance and stores them in a buffer).
* Such coupling may occur when the storage is an {@link InputStream}, an {@link java.io.OutputStream} or a
@@ -269,7 +269,7 @@ public class StorageConnector implements Serializable {
*/
private static final class Coupled {
/**
- * The {@link StorageConnector#storage} viewed as another kind of object.
+ * The {@link UnsafeConnector#storage} viewed as another kind of object.
* Supported types are:
*
* <ul>
@@ -300,7 +300,7 @@ public class StorageConnector implements Serializable {
/**
* The object that {@link #view} is wrapping. For example if {@code view} is an {@link InputStreamReader},
* then {@code wrapperFor.view} is an {@link InputStream}. This field is {@code null} if {@link #view} ==
- * {@link StorageConnector#storage}.
+ * {@link UnsafeConnector#storage}.
*/
final Coupled wrapperFor;
@@ -321,7 +321,7 @@ public class StorageConnector implements Serializable {
boolean isValid;
/**
- * Creates a wrapper for {@link StorageConnector#storage}. This constructor is used when we need to create
+ * Creates a wrapper for {@link UnsafeConnector#storage}. This constructor is used when we need to create
* a {@code Coupled} instance for another view wrapping {@code storage}.
*/
Coupled(final Object storage) {
@@ -374,7 +374,7 @@ public class StorageConnector implements Serializable {
/**
* Declares as invalid all unsynchronized {@code Coupled} instances which are used, directly or indirectly,
- * by this instance. This method is invoked before {@link StorageConnector#getStorageAs(Class)} returns a
+ * by this instance. This method is invoked before {@link UnsafeConnector#getStorageAs(Class)} returns a
* view, in order to remember which views would need to be resynchronized if they are requested.
*/
final void invalidateSources() {
@@ -387,7 +387,7 @@ public class StorageConnector implements Serializable {
/**
* Declares as invalid all unsynchronized {@code Coupled} instances which are using, directly or indirectly,
- * this instance. This method is invoked before {@link StorageConnector#getStorageAs(Class)} returns a view,
+ * this instance. This method is invoked before {@link UnsafeConnector#getStorageAs(Class)} returns a view,
* in order to remember which views would need to be resynchronized if they are requested.
*/
final void invalidateUsages() {
@@ -406,7 +406,7 @@ public class StorageConnector implements Serializable {
* wrapped by this {@code Coupled}. This method identifies only the views that <em>use</em> this view;
* it does not identify the views <em>used</em> by this view.
*
- * This method is for {@link StorageConnector#closeAllExcept(Object)} internal usage.
+ * This method is for {@link UnsafeConnector#closeAllExcept(Object)} internal usage.
*
* @param toClose the map where to write the list of views to not close.
*/
@@ -475,9 +475,9 @@ public class StorageConnector implements Serializable {
} else if (view instanceof Channel) {
/*
* Searches for a ChannelDataInput wrapping the channel, because it contains the original position
- * (note: StorageConnector tries to instantiate ChannelDataInput in priority to all other types).
+ * (note: UnsafeConnector tries to instantiate ChannelDataInput in priority to all other types).
* If we don't find any, this is considered as a non-seekable channel (we do not assume that the
- * channel original position was 0 when the user gave it to StorageConnector).
+ * channel original position was 0 when the user gave it to UnsafeConnector).
*/
String name = null;
if (wrappedBy != null) {
@@ -518,11 +518,11 @@ public class StorageConnector implements Serializable {
/**
* Formats the current {@code Coupled} and all its children as a tree in the given tree table node.
- * This method is used for {@link StorageConnector#toString()} implementation only and may change
+ * This method is used for {@link UnsafeConnector#toString()} implementation only and may change
* in any future version.
*
* @param appendTo where to write name, value and children.
- * @param views reference to the {@link StorageConnector#views} map. Will be read only.
+ * @param views reference to the {@link UnsafeConnector#views} map. Will be read only.
*/
@Debug
final void append(final TreeTable.Node appendTo, final Map<Class<?>, Coupled> views) {
@@ -549,7 +549,7 @@ public class StorageConnector implements Serializable {
*
* @param storage the input/output object as a URL, file, image input stream, <i>etc.</i>.
*/
- public StorageConnector(final Object storage) {
+ public UnsafeConnector(final Object storage) {
ArgumentChecks.ensureNonNull("storage", storage);
this.storage = storage;
}
@@ -747,20 +747,20 @@ public class StorageConnector implements Serializable {
* </li>
* </ul>
*
- * Multiple invocations of this method on the same {@code StorageConnector} instance will try
+ * Multiple invocations of this method on the same {@code UnsafeConnector} instance will try
* to return the same instance on a <cite>best effort</cite> basis. Consequently, implementations of
* {@link DataStoreProvider#probeContent(StorageConnector)} methods shall not close the stream or
- * database connection returned by this method. In addition, those {@code probeContent(StorageConnector)}
+ * database connection returned by this method. In addition, those {@code probeContent(UnsafeConnector)}
* methods are responsible for restoring the stream or byte buffer to its original position on return.
*
* @param <T> the compile-time type of the {@code type} argument.
* @param type the desired type as one of {@code ByteBuffer}, {@code DataInput}, {@code Connection}
- * class or other type supported by {@code StorageConnector} subclasses.
+ * class or other type supported by {@code UnsafeConnector} subclasses.
* @return the storage as a view of the given type, or {@code null} if the given type is one of the supported
* types listed in javadoc but no view can be created for the source given at construction time.
* @throws IllegalArgumentException if the given {@code type} argument is not one of the supported types
* listed in this javadoc or in subclass javadoc.
- * @throws IllegalStateException if this {@code StorageConnector} has been {@linkplain #closeAllExcept closed}.
+ * @throws IllegalStateException if this {@code UnsafeConnector} has been {@linkplain #closeAllExcept closed}.
* @throws DataStoreException if an error occurred while opening a stream or database connection.
*
* @see #getStorage()
@@ -826,7 +826,7 @@ public class StorageConnector implements Serializable {
view = ObjectConverters.convert(storage, type);
} catch (UnconvertibleObjectException e) {
if (!OPENERS.containsKey(type)) throw e;
- Logging.recoverableException(StoreUtilities.LOGGER, StorageConnector.class, "getStorageAs", e);
+ Logging.recoverableException(StoreUtilities.LOGGER, UnsafeConnector.class, "getStorageAs", e);
view = null;
}
addView(type, view);
@@ -906,7 +906,7 @@ public class StorageConnector implements Serializable {
* Creates a view for the input as a {@link ChannelDataInput} if possible.
* This is also a starting point for {@link #createDataInput()} and {@link #createByteBuffer()}.
* This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.
+ * {@code UnsafeConnector} instance.
*
* @param asImageInputStream whether the {@code ChannelDataInput} needs to be {@link ChannelImageInputStream} subclass.
* @throws IOException if an error occurred while opening a channel for the input.
@@ -966,7 +966,7 @@ public class StorageConnector implements Serializable {
* the {@code ByteBuffer.class} key together with the {@code DataInput.class} case.
*
* <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
+ * {@code UnsafeConnector} instance.</p>
*
* @throws IOException if an error occurred while opening a stream for the input.
*/
@@ -1015,7 +1015,7 @@ public class StorageConnector implements Serializable {
* to {@link #prefetch()}.
*
* <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
+ * {@code UnsafeConnector} instance.</p>
*
* @throws IOException if an error occurred while opening a stream for the input.
*/
@@ -1111,7 +1111,7 @@ public class StorageConnector implements Serializable {
* {@link ChannelImageInputStream}, this cast is usually possible.
*
* <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
+ * {@code UnsafeConnector} instance.</p>
*/
private ImageInputStream createImageInputStream() throws DataStoreException {
final Class<DataInput> source = DataInput.class;
@@ -1130,7 +1130,7 @@ public class StorageConnector implements Serializable {
* otherwise.
*
* <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
+ * {@code UnsafeConnector} instance.</p>
*/
private InputStream createInputStream() throws IOException, DataStoreException {
final Class<DataInput> source = DataInput.class;
@@ -1157,7 +1157,7 @@ public class StorageConnector implements Serializable {
* Creates a character reader if possible.
*
* <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
+ * {@code UnsafeConnector} instance.</p>
*/
private Reader createReader() throws IOException, DataStoreException {
final InputStream input = getStorageAs(InputStream.class);
@@ -1175,7 +1175,7 @@ public class StorageConnector implements Serializable {
* Creates a database connection if possible.
*
* <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
+ * {@code UnsafeConnector} instance.</p>
*/
private Connection createConnection() throws SQLException {
if (storage instanceof DataSource) {
@@ -1190,7 +1190,7 @@ public class StorageConnector implements Serializable {
* Returns the storage as a path if possible, or {@code null} otherwise.
*
* <p>This method is one of the {@link #OPENERS} methods and should be invoked at most once per
- * {@code StorageConnector} instance.</p>
+ * {@code UnsafeConnector} instance.</p>
*/
private String createString() {
return IOUtilities.toString(storage);
@@ -1258,7 +1258,7 @@ public class StorageConnector implements Serializable {
}
/**
- * Closes all streams and connections created by this {@code StorageConnector} except the given view.
+ * Closes all streams and connections created by this {@code UnsafeConnector} except the given view.
* This method closes all objects created by the {@link #getStorageAs(Class)} method except the given {@code view}.
* If {@code view} is {@code null}, then this method closes everything including the {@linkplain #getStorage()
* storage} if it is closeable.
@@ -1267,7 +1267,7 @@ public class StorageConnector implements Serializable {
* by the data store is given in argument to this method - or when no suitable {@code DataStore} has been
* found - in which case the {@code view} argument is null.</p>
*
- * <p>This {@code StorageConnector} instance shall not be used anymore after invocation of this method.</p>
+ * <p>This {@code UnsafeConnector} instance shall not be used anymore after invocation of this method.</p>
*
* @param view the view to leave open, or {@code null} if none.
* @throws DataStoreException if an error occurred while closing the stream or database connection.
@@ -1277,7 +1277,7 @@ public class StorageConnector implements Serializable {
*/
public void closeAllExcept(final Object view) throws DataStoreException {
if (views == null) {
- views = Collections.emptyMap(); // For blocking future usage of this StorageConnector instance.
+ views = Collections.emptyMap(); // For blocking future usage of this UnsafeConnector instance.
if (storage != view && storage instanceof AutoCloseable) try {
((AutoCloseable) storage).close();
} catch (DataStoreException e) {
@@ -1346,7 +1346,7 @@ public class StorageConnector implements Serializable {
}
}
}
- views = Collections.emptyMap(); // For blocking future usage of this StorageConnector instance.
+ views = Collections.emptyMap(); // For blocking future usage of this UnsafeConnector instance.
/*
* Now close all remaining items. Typically (but not necessarily) there is only one remaining item.
* If an exception occurs, we will propagate it only after we are done closing all items.
@@ -1369,10 +1369,10 @@ public class StorageConnector implements Serializable {
}
/**
- * Returns a string representation of this {@code StorageConnector} for debugging purpose.
+ * Returns a string representation of this {@code UnsafeConnector} for debugging purpose.
* This string representation is for debugging purpose only and may change in any future version.
*
- * @return a string representation of this {@code StorageConnector} for debugging purpose.
+ * @return a string representation of this {@code UnsafeConnector} for debugging purpose.
*/
@Override
public String toString() {
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java
index 381bec8..1079ea6 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java
@@ -65,27 +65,27 @@ public final strictfp class StorageConnectorTest extends TestCase {
/**
* Name of the test file, in the same directory than this {@code StorageConnectorTest} file.
*/
- private static final String FILENAME = "Any.txt";
+ static final String FILENAME = "Any.txt";
/**
* Beginning of the first sentence in {@value #FILENAME}.
*/
- private static final String FIRST_SENTENCE = "The purpose of this file";
+ static final String FIRST_SENTENCE = "The purpose of this file";
/**
* The 4 first characters of {@link #FIRST_SENTENCE}, encoded as an integer.
*/
- private static final int MAGIC_NUMBER = ('T' << 24) | ('h' << 16) | ('e' << 8) | ' ';
+ static final int MAGIC_NUMBER = ('T' << 24) | ('h' << 16) | ('e' << 8) | ' ';
/**
* Creates the instance to test. This method uses the {@code "test.txt"} ASCII file as
* the resource to test. The resource can be provided either as a URL or as a stream.
*/
- private static StorageConnector create(final boolean asStream) {
+ static UnsafeConnector create(final boolean asStream) {
final Class<?> c = StorageConnectorTest.class;
final Object storage = asStream ? c.getResourceAsStream(FILENAME) : c.getResource(FILENAME);
assertNotNull(storage);
- final StorageConnector connector = new StorageConnector(storage);
+ final UnsafeConnector connector = new UnsafeConnector(storage);
connector.setOption(OptionKey.ENCODING, StandardCharsets.US_ASCII);
connector.setOption(OptionKey.URL_ENCODING, "UTF-8");
return connector;
@@ -96,7 +96,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
*/
@Test
public void testGetStorageName() {
- final StorageConnector c = create(false);
+ final UnsafeConnector c = create(false);
assertEquals(FILENAME, c.getStorageName());
}
@@ -105,7 +105,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
*/
@Test
public void testGetExtension() {
- final StorageConnector c = create(false);
+ final UnsafeConnector c = create(false);
assertEquals("txt", c.getFileExtension());
}
@@ -117,7 +117,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
*/
@Test
public void testGetAsString() throws DataStoreException, IOException {
- final StorageConnector c = create(false);
+ final UnsafeConnector c = create(false);
assertTrue(c.getStorageAs(String.class).endsWith("org/apache/sis/storage/" + FILENAME));
}
@@ -149,7 +149,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
* Implementation of {@link #testGetAsDataInputFromURL()} and {@link #testGetAsDataInputFromStream()}.
*/
private void testGetAsDataInput(final boolean asStream) throws DataStoreException, IOException {
- final StorageConnector connection = create(asStream);
+ final UnsafeConnector connection = create(asStream);
final DataInput input = connection.getStorageAs(DataInput.class);
assertSame("Value shall be cached.", input, connection.getStorageAs(DataInput.class));
assertInstanceOf("Needs the SIS implementation.", ChannelImageInputStream.class, input);
@@ -175,7 +175,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
@Test
@DependsOnMethod("testGetAsDataInputFromURL")
public void testGetAsImageInputStream() throws DataStoreException, IOException {
- final StorageConnector connection = create(false);
+ final UnsafeConnector connection = create(false);
final ImageInputStream in = connection.getStorageAs(ImageInputStream.class);
assertSame(connection.getStorageAs(DataInput.class), in);
connection.closeAllExcept(null);
@@ -191,7 +191,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
@Test
@DependsOnMethod("testGetAsImageInputStream")
public void testGetOriginalInputStream() throws DataStoreException, IOException {
- final StorageConnector connection = create(true);
+ final UnsafeConnector connection = create(true);
final InputStream in = connection.getStorageAs(InputStream.class);
assertSame("The InputStream shall be the one specified to the constructor.", connection.getStorage(), in);
/*
@@ -226,7 +226,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
@Test
@DependsOnMethod("testGetAsImageInputStream")
public void testGetAsInputStream() throws DataStoreException, IOException {
- final StorageConnector connection = create(false);
+ final UnsafeConnector connection = create(false);
final InputStream in = connection.getStorageAs(InputStream.class);
assertNotSame(connection.getStorage(), in);
assertSame("Expected cached value.", in, connection.getStorageAs(InputStream.class));
@@ -251,7 +251,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
@Test
@DependsOnMethod({"testGetAsInputStream", "testGetAsDataInputFromStream"})
public void testGetAsReader() throws DataStoreException, IOException {
- final StorageConnector connection = create(true);
+ final UnsafeConnector connection = create(true);
final Reader in = connection.getStorageAs(Reader.class);
final char[] expected = FIRST_SENTENCE.toCharArray();
final char[] actual = new char[expected.length];
@@ -291,7 +291,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
*/
@Test
public void testGetAsChannelDataInput() throws DataStoreException, IOException {
- final StorageConnector connection = create(true);
+ final UnsafeConnector connection = create(true);
final ChannelDataInput input = connection.getStorageAs(ChannelDataInput.class);
assertFalse(input instanceof ChannelImageInputStream);
assertEquals(MAGIC_NUMBER, input.buffer.getInt());
@@ -317,10 +317,10 @@ public final strictfp class StorageConnectorTest extends TestCase {
@Test
@DependsOnMethod("testGetAsDataInputFromURL")
public void testGetAsByteBuffer() throws DataStoreException, IOException {
- final StorageConnector connection = create(false);
+ final UnsafeConnector connection = create(false);
final ByteBuffer buffer = connection.getStorageAs(ByteBuffer.class);
assertNotNull("getStorageAs(ByteBuffer.class)", buffer);
- assertEquals(StorageConnector.DEFAULT_BUFFER_SIZE, buffer.capacity());
+ assertEquals(UnsafeConnector.DEFAULT_BUFFER_SIZE, buffer.capacity());
assertEquals(MAGIC_NUMBER, buffer.getInt());
connection.closeAllExcept(null);
}
@@ -337,15 +337,15 @@ public final strictfp class StorageConnectorTest extends TestCase {
@Test
@DependsOnMethod("testGetAsDataInputFromStream")
public void testGetAsTemporaryByteBuffer() throws DataStoreException, IOException {
- StorageConnector connection = create(true);
+ UnsafeConnector connection = create(true);
final DataInput in = ImageIO.createImageInputStream(connection.getStorage());
assertNotNull("ImageIO.createImageInputStream(InputStream)", in); // Sanity check.
- connection = new StorageConnector(in);
+ connection = new UnsafeConnector(in);
assertSame(in, connection.getStorageAs(DataInput.class));
final ByteBuffer buffer = connection.getStorageAs(ByteBuffer.class);
assertNotNull("getStorageAs(ByteBuffer.class)", buffer);
- assertEquals(StorageConnector.MINIMAL_BUFFER_SIZE, buffer.capacity());
+ assertEquals(UnsafeConnector.MINIMAL_BUFFER_SIZE, buffer.capacity());
assertEquals(MAGIC_NUMBER, buffer.getInt());
connection.closeAllExcept(null);
}
@@ -357,7 +357,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
* @throws IOException should never happen since we do not open any file.
*/
public void testGetAsConnection() throws DataStoreException, IOException {
- final StorageConnector connection = create(false);
+ final UnsafeConnector connection = create(false);
assertNull(connection.getStorageAs(Connection.class));
connection.closeAllExcept(null);
}
@@ -370,7 +370,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
*/
@Test
public void testGetInvalidObject() throws DataStoreException {
- final StorageConnector connection = create(true);
+ final UnsafeConnector connection = create(true);
assertNotNull("getStorageAs(InputStream.class)", connection.getStorageAs(InputStream.class));
assertNull ("getStorageAs(URI.class)", connection.getStorageAs(URI.class));
assertNull ("getStorageAs(String.class)", connection.getStorageAs(String.class));
@@ -393,7 +393,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
@Test
@DependsOnMethod("testGetAsDataInputFromStream")
public void testCloseAllExcept() throws DataStoreException, IOException {
- final StorageConnector connection = create(true);
+ final UnsafeConnector connection = create(true);
final DataInput input = connection.getStorageAs(DataInput.class);
final ReadableByteChannel channel = ((ChannelImageInputStream) input).channel;
assertTrue("channel.isOpen()", channel.isOpen());
@@ -404,7 +404,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
@Test
public void getting_buffer_should_not_change_underlying_stream_position() throws Exception {
- final StorageConnector con = create(false);
+ final UnsafeConnector con = create(false);
final ImageInputStream stream = con.getStorageAs(ImageInputStream.class);
@@ -421,7 +421,7 @@ public final strictfp class StorageConnectorTest extends TestCase {
public void moving_stream_should_not_impact_buffer() throws Exception {
final byte[] data = new byte[(int) Math.pow(2, 16)];
new Random().nextBytes(data);
- final StorageConnector connector = new StorageConnector(new ByteArrayInputStream(data));
+ final UnsafeConnector connector = new UnsafeConnector(new ByteArrayInputStream(data));
final ByteBuffer buffer = connector.getStorageAs(ByteBuffer.class);
final int blockSize = buffer.remaining();
final byte[] ctrl = new byte[blockSize];
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/StrictStorageConnectorTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/StrictStorageConnectorTest.java
index 266fd05..d58ecf4 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/StrictStorageConnectorTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/StrictStorageConnectorTest.java
@@ -3,19 +3,16 @@ package org.apache.sis.storage;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import org.apache.sis.setup.OptionKey;
-import org.apache.sis.storage.StrictStorageConnector.StorageControlException;
+import org.apache.sis.storage.StorageConnector.StorageControlException;
import org.apache.sis.test.DependsOn;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -30,15 +27,8 @@ public class StrictStorageConnectorTest {
* Creates the instance to test. This method uses the {@code "test.txt"} ASCII file as
* the resource to test. The resource can be provided either as a URL or as a stream.
*/
- private static StrictStorageConnector create(final boolean asStream) {
- final Class<?> c = StorageConnectorTest.class;
- final Object storage = asStream ? c.getResourceAsStream(FILENAME) : c.getResource(FILENAME);
- assertNotNull(storage);
- final StorageConnector unsafeConnector = new StorageConnector(storage);
- unsafeConnector.setOption(OptionKey.ENCODING, StandardCharsets.US_ASCII);
- unsafeConnector.setOption(OptionKey.URL_ENCODING, "UTF-8");
- final StrictStorageConnector connector = new StrictStorageConnector(unsafeConnector);
- return connector;
+ private static StorageConnector create(final boolean asStream) {
+ return new StorageConnector(StorageConnectorTest.create(asStream));
}
private static byte[] getFileBytes() throws URISyntaxException, IOException {
@@ -48,7 +38,7 @@ public class StrictStorageConnectorTest {
@Test
public void acquiring_path_works() {
- final StrictStorageConnector connector = create(false);
+ final StorageConnector connector = create(false);
assertTrue(connector.getPath().isPresent());
assertTrue(connector.getURI().isPresent());
assertTrue(connector.getPathAsString().isPresent());
@@ -57,7 +47,7 @@ public class StrictStorageConnectorTest {
@Test
public void stream_based_connector_return_empty_path() {
- final StrictStorageConnector connector = create(true);
+ final StorageConnector connector = create(true);
assertFalse(connector.getPath().isPresent());
assertFalse(connector.getURI().isPresent());
assertFalse(connector.getPathAsString().isPresent());
@@ -67,7 +57,7 @@ public class StrictStorageConnectorTest {
@Test
public void byte_buffer_is_rewind_after_use() throws Exception {
final byte[] ctrl = getFileBytes();
- try (final StrictStorageConnector connector = create(false)) {
+ try (final StorageConnector connector = create(false)) {
// Mess with internal buffer
connector.useAsBuffer(buffer -> {
// mess with it
@@ -86,7 +76,7 @@ public class StrictStorageConnectorTest {
@Test
public void fail_fast_when_user_corrupts_stream_mark() throws IOException, DataStoreException {
- try (final StrictStorageConnector c = create(false)) {
+ try (final StorageConnector c = create(false)) {
try {
c.useAsImageInputStream(stream -> {
stream.skipBytes(1);
@@ -102,7 +92,7 @@ public class StrictStorageConnectorTest {
@Test
public void no_concurrency_allowed() throws Exception {
- try (final StrictStorageConnector c = create(false)) {
+ try (final StorageConnector c = create(false)) {
synchronized (c) {
new Thread(() -> {
try {
@@ -142,7 +132,7 @@ public class StrictStorageConnectorTest {
@Test
public void commit_close_all_resources_but_chosen() throws Exception {
final InputStream is;
- try (final StrictStorageConnector c = create(false)) {
+ try (final StorageConnector c = create(false)) {
is = c.commit(InputStream.class);
@@ -168,7 +158,7 @@ public class StrictStorageConnectorTest {
@Test
public void closing_multiple_times_causes_no_error() throws Exception {
- try ( StrictStorageConnector c = create(true) ) {
+ try ( StorageConnector c = create(true) ) {
c.commit(InputStream.class);
c.closeAllExcept(null);
[sis] 01/02: chore(Storage): mirror Storage connector API in strict
version
Posted by am...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
amanin pushed a commit to branch refactor/strict_storage_connector
in repository https://gitbox.apache.org/repos/asf/sis.git
commit eb8954b9960c1515fd9965fc8498a636e0690bda
Author: Alexis Manin <al...@geomatys.com>
AuthorDate: Wed Mar 24 13:27:40 2021 +0100
chore(Storage): mirror Storage connector API in strict version
---
.../apache/sis/storage/StrictStorageConnector.java | 42 ++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/StrictStorageConnector.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/StrictStorageConnector.java
index 22754c7..23934cc 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/StrictStorageConnector.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/StrictStorageConnector.java
@@ -267,7 +267,44 @@ public class StrictStorageConnector implements AutoCloseable {
return storage.getOption(key);
}
+ public <T> void setOption(final OptionKey<T> key, final T value) {
+ storage.setOption(key, value);
+ }
+
+ public String getStorageName() {
+ return storage.getStorageName();
+ }
+
+ public String getFileExtension() {
+ return storage.getFileExtension();
+ }
+
/**
+ * Please use a safe alternative instead, like {@link #useAs(Class, StorageOperatingFunction)}.
+ * Only call this if safe methods do not support the storage type you require.
+ *
+ * @return A connector that does not provides control over resource usage. It does not automatically rewind
+ * automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
+ */
+ public <T> T getStorageAs(final Class<T> type) throws IllegalArgumentException, DataStoreException {
+ return storage.getStorageAs(type);
+ }
+
+ /**
+ * Please use a safe alternative instead, like {@link #useAs(Class, StorageOperatingFunction)}.
+ * Only call this if safe methods do not support the storage type you require.
+ *
+ * @return A connector that does not provides control over resource usage. It does not automatically rewind
+ * automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
+ */
+ @Deprecated
+ public Object getStorage() throws DataStoreException {
+ return storage.getStorage();
+ }
+
+ /**
+ * Please try to use {@link #useAs(Class, StorageOperatingFunction)} instead. Only call this if safe methods do not
+ * support the storage type you require.
*
* @return A connector that does not provides control over resource usage. It does not automatically rewind
* automatically used resources. That means that resource lifecycle becomes <em>entirely</em> caller responsability.
@@ -276,6 +313,11 @@ public class StrictStorageConnector implements AutoCloseable {
return storage;
}
+ @Override
+ public String toString() {
+ return storage.toString();
+ }
+
private interface StorageCallable<V> extends Callable<V> {
@Override
V call() throws IOException, DataStoreException;