sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tlpin...@apache.org
Subject svn commit: r1517321 [16/16] - in /sis/branches/Shapefile: ./ application/ application/sis-console/ application/sis-console/src/main/artifact/ application/sis-console/src/main/java/org/apache/sis/console/ application/sis-console/src/main/resources/org/...
Date Sun, 25 Aug 2013 15:49:59 GMT
Modified: sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java [UTF-8] Sun Aug 25 15:49:51 2013
@@ -16,6 +16,11 @@
  */
 package org.apache.sis.internal.storage;
 
+import java.util.Set;
+import java.util.List;
+import java.util.HashSet;
+import java.util.Collections;
+import java.util.Arrays;
 import java.util.Locale;
 import java.io.File;
 import java.io.FileInputStream;
@@ -28,13 +33,16 @@ import java.net.URISyntaxException;
 import java.net.MalformedURLException;
 import java.nio.channels.Channels;
 import java.nio.channels.ReadableByteChannel;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.resources.Errors;
 
 // Related to JDK7
+import org.apache.sis.internal.jdk7.Files;
 import org.apache.sis.internal.jdk7.StandardCharsets;
+import org.apache.sis.internal.jdk7.StandardOpenOption;
 
 
 /**
@@ -49,11 +57,17 @@ import org.apache.sis.internal.jdk7.Stan
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @since   0.3 (derived from geotk-3.00)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public final class IOUtilities extends Static {
     /**
+     * Options to be rejected by {@link #open(Object, String, OpenOption[])} for safety reasons.
+     */
+    private static final List<String> ILLEGAL_OPTIONS = Arrays.asList( // EnumSet of StandardOpenOption on JDK7 branch
+            StandardOpenOption.APPEND, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DELETE_ON_CLOSE);
+
+    /**
      * Do not allow instantiation of this class.
      */
     private IOUtilities() {
@@ -276,14 +290,12 @@ public final class IOUtilities extends S
             return new File(uri);
         } catch (IllegalArgumentException cause) {
             /*
-             * Typically happen when the URI contains fragment that can not be represented
-             * in a File (e.g. a Query part), so it could be considered as if the URI with
-             * the fragment part can not represent an existing file.
+             * Typically happen when the URI scheme is not "file". But may also happen if the
+             * URI contains fragment that can not be represented in a File (e.g. a Query part).
+             * The IllegalArgumentException does not allow us to distinguish those cases.
              */
-            final MalformedURLException e = new MalformedURLException(Exceptions.formatChainedMessages(
-                    null, Errors.format(Errors.Keys.IllegalArgumentValue_2, "URL", url), cause));
-            e.initCause(cause);
-            throw e;
+            throw new IOException(Exceptions.formatChainedMessages(null,
+                    Errors.format(Errors.Keys.IllegalArgumentValue_2, "URL", url), cause), cause);
         }
     }
 
@@ -297,10 +309,6 @@ public final class IOUtilities extends S
      * A URL can represent a file, but {@link URL#openStream()} appears to return a {@code BufferedInputStream}
      * wrapping the {@link FileInputStream}, which is not a desirable feature when we want to obtain a channel.
      *
-     * <p>There is no {@code toPathOrURL} methods because {@link Path} can be associated to various file systems.
-     * It would be possible (not necessarily desirable, but at least doable) do create {@code Path} for HTTP or
-     * FTP protocols.</p>
-     *
      * @param  path The path to convert, or {@code null}.
      * @param  encoding If the URL is encoded in a {@code application/x-www-form-urlencoded}
      *         MIME format, the character encoding (normally {@code "UTF-8"}). If the URL is
@@ -313,7 +321,10 @@ public final class IOUtilities extends S
         if (path == null) {
             return null;
         }
-        // Check if the path seems to be an ordinary file.
+        /*
+         * Check if the path seems to be a local file. Those paths are assumed never encoded.
+         * The heuristic rules applied here may change in any future SIS version.
+         */
         if (path.indexOf('?') < 0 && path.indexOf('#') < 0) {
             final int s = path.indexOf(':');
             /*
@@ -326,9 +337,15 @@ public final class IOUtilities extends S
             }
         }
         final URL url = new URL(path);
-        if (url.getProtocol().equalsIgnoreCase("file")) {
+        final String scheme = url.getProtocol();
+        if (scheme != null && scheme.equalsIgnoreCase("file")) {
             return toFile(url, encoding);
         }
+        /*
+         * Leave the URL in its original encoding on the assumption that this is the encoding expected by
+         * the server. This is different than the policy for URI, because the later are always in UTF-8.
+         * If a URI is needed, callers should use toURI(url, encoding).
+         */
         return url;
     }
 
@@ -344,41 +361,116 @@ public final class IOUtilities extends S
      *       or {@link CharSequence}, then a new channel is opened.</li>
      * </ul>
      *
+     * The given options are used for opening the channel on a <em>best effort basis</em>.
+     * In particular, even if the caller provided the {@code WRITE} option, he still needs
+     * to verify if the returned channel implements {@link java.nio.channels.WritableByteChannel}.
+     * This is because the channel may be opened by {@link URL#openStream()}, in which case the
+     * options are ignored.
+     *
+     * <p>The following options are illegal and will cause an exception to be thrown if provided:
+     * {@code APPEND}, {@code TRUNCATE_EXISTING}, {@code DELETE_ON_CLOSE}. We reject those options
+     * because this method is primarily designed for readable channels, with optional data edition.
+     * Since the write option is not guaranteed to be honored, we have to reject the options that
+     * would alter significatively the channel behavior depending on whether we have been able to
+     * honor the options or not.</p>
+     *
      * @param  input The file to open, or {@code null}.
-     * @param  encoding If the URL is encoded in a {@code application/x-www-form-urlencoded}
-     *         MIME format, the character encoding (normally {@code "UTF-8"}). If the URL is
-     *         not encoded, then {@code null}. This argument is ignored if the given path does
-     *         not need to be converted from URL to {@code File}.
-     * @return The input stream for the given file, or {@code null} if the given type is unknown.
+     * @param  encoding If the input is an encoded URL, the character encoding (normally {@code "UTF-8"}).
+     *         If the URL is not encoded, then {@code null}. This argument is ignored if the given input
+     *         does not need to be converted from URL to {@code File}.
+     * @param  options The options to use for creating a new byte channel. Can be null or empty for read-only.
+     * @return The channel for the given input, or {@code null} if the given input is of unknown type.
      * @throws IOException If an error occurred while opening the given file.
      */
-    public static ReadableByteChannel open(Object input, final String encoding) throws IOException {
+    public static ReadableByteChannel open(Object input, final String encoding, Object... options) throws IOException {
+        /*
+         * Unconditionally verify the options, even if we may not use them.
+         */
+        final Set<Object> optionSet;
+        if (options == null || options.length == 0) {
+            optionSet = Collections.emptySet();
+        } else {
+            optionSet = new HashSet<Object>(Arrays.asList(options));
+            optionSet.add(StandardOpenOption.READ);
+            if (optionSet.removeAll(ILLEGAL_OPTIONS)) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
+                        "options", Arrays.toString(options)));
+            }
+        }
+        /*
+         * Check for inputs that are already readable channels or input streams. We perform an explicit check
+         * for FileInputStream even if  Channels.newChannel(InputStream)  does this check implicitly, because
+         * Channels.newChannel(…) restricts itself to the exact FileInputStream class while we want to invoke
+         * getChannel() for any subclasses.
+         */
         if (input instanceof ReadableByteChannel) {
             return (ReadableByteChannel) input;
         }
         if (input instanceof InputStream) {
-            /*
-             * Channels.newChannel(InputStream) checks for FileInputStream, but it requires that exact class.
-             * Concequently we have to perform our own check if we want to allow any FileInputStream subclass
-             * to have their 'getChannel()' method invoked.
-             */
             if (input instanceof FileInputStream) {
                 return ((FileInputStream) input).getChannel();
             }
             return Channels.newChannel((InputStream) input);
         }
-        if (input instanceof CharSequence) { // Needs to be before the check for File or URL.
-            input = toFileOrURL(input.toString(), encoding);
-        }
-        if (input instanceof File) {
-            return new FileInputStream((File) input).getChannel();
-        }
-        if (input instanceof URI) { // Needs to be before the check for URL.
-            input = ((URI) input).toURL();
+        // NOTE: Many comments below this point actually apply to the JDK7 branch.
+        //       We keep them here for making easier the synchonization between the branches.
+        /*
+         * In the following cases, we will try hard to convert to Path objects before to fallback
+         * on File, URL or URI, because only Path instances allow us to use the given OpenOptions.
+         */
+        if (input instanceof URL) {
+            try {
+                input = toFile((URL) input, encoding);
+            } catch (IOException e) {
+                // This is normal if the URL uses HTTP or FTP protocol for instance.
+                // Log the exception at FINE level without stack trace. We will open
+                // the channel later using the URL instead than using the Path.
+                recoverableException(e);
+            }
+        } else if (input instanceof URI) {
+            /*
+             * If the user gave us a URI, try to convert to a Path before to fallback to URL, in order to be
+             * able to use the given OpenOptions.  Note that the conversion to Path is likely to fail if the
+             * URL uses HTTP or FTP protocols, because JDK7 does not provide file systems for them by default.
+             */
+            final URI uri = (URI) input;
+            if (!uri.isAbsolute()) {
+                // All methods invoked in this block throws IllegalArgumentException if the URI has no scheme,
+                // so we are better to check now and provide a more appropriate exception for this method.
+                throw new IOException(Errors.format(Errors.Keys.MissingSchemeInURI));
+            } else try {
+                input = new File(uri);
+            } catch (IllegalArgumentException e) {
+                input = uri.toURL();
+                // We have been able to convert to URL, but the given OpenOptions may not be used.
+                // Log the exception at FINE level without stack trace, because the exception is
+                // probably a normal behavior in this context.
+                recoverableException(e);
+            }
+        } else {
+            if (input instanceof CharSequence) { // Needs to be before the check for File or URL.
+                input = toFileOrURL(input.toString(), encoding);
+            }
         }
+        /*
+         * One last check for URL. The URL may be either the given input if we have not been able
+         * to convert it to a Path, or a URI, File or CharSequence input converted to URL. Do not
+         * try to convert the URL to a Path, because this has already been tried before this point.
+         */
         if (input instanceof URL) {
             return Channels.newChannel(((URL) input).openStream());
         }
+        if (input instanceof File) {
+            return Files.newByteChannel((File) input, optionSet);
+        }
         return null;
     }
+
+    /**
+     * Invoked for reporting exceptions that may be normal behavior. This method logs
+     * the exception at {@link java.util.logging.Level#FINE} without stack trace.
+     */
+    private static void recoverableException(final Exception warning) {
+        Logging.recoverableException(Logging.getLogger("org.apache.sis.storage"), IOUtilities.class, "open", warning);
+    }
 }

Modified: sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java [UTF-8] Sun Aug 25 15:49:51 2013
@@ -29,13 +29,20 @@ import org.apache.sis.internal.jdk7.Auto
 
 
 /**
- * A storage object which manage a series of features, coverages or sensor data.
+ * Manages a series of features, coverages or sensor data.
+ *
+ * {@section Thread safety policy}
+ * This {@code DataStore} base class is thread-safe. However subclasses are usually not.
+ * Unless otherwise specified by subclasses, users should assume that {@code DataStore}
+ * instances are not thread-safe.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
  * @version 0.3
  * @module
+ *
+ * @see DataStores#open(Object)
  */
 public abstract class DataStore implements Localized, AutoCloseable {
     /**

Modified: sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java [UTF-8] Sun Aug 25 15:49:51 2013
@@ -16,16 +16,42 @@
  */
 package org.apache.sis.storage;
 
+import org.apache.sis.util.ThreadSafe;
+
 
 /**
- * Creates {@link DataStore} instances for a specific format from a given {@link StorageConnector} input.
- * There is typically a different {@code DataStoreProvider} instance for each format provided by a library.
+ * Provides information about a specific {@link DataStore} implementation.
+ * There is typically one {@code DataStoreProvider} instance for each format supported by a library.
+ * Each {@code DataStoreProvider} instances provides the following services:
+ *
+ * <ul>
+ *   <li>Provide generic information about the storage (name, <i>etc.</i>).</li>
+ *   <li>Create instances of the {@link DataStore} implementation described by this provider.</li>
+ *   <li>Test if a {@code DataStore} instance created by this provider would have reasonable chances
+ *       to open a given {@link StorageConnector}.</li>
+ * </ul>
+ *
+ * {@section Packaging data stores}
+ * JAR files that provide implementations of this class shall contain an entry with exactly the following path:
+ *
+ * {@preformat text
+ *     META-INF/services/org.apache.sis.storage.DataStoreProvider
+ * }
+ *
+ * The above entry shall contain one line for each {@code DataStoreProvider} implementation provided in the JAR file,
+ * where each line is the fully qualified name of the implementation class.
+ * See {@link java.util.ServiceLoader} for more general discussion about this lookup mechanism.
+ *
+ * {@section Thread safety policy}
+ * All {@code DataStoreProvider} implementations shall be thread-safe.
+ * However the {@code DataStore} instances created by the providers do not need to be thread-safe.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.4
  * @module
  */
+@ThreadSafe
 public abstract class DataStoreProvider {
     /**
      * Creates a new provider.
@@ -34,57 +60,94 @@ public abstract class DataStoreProvider 
     }
 
     /**
-     * Returns {@code TRUE} if the given storage appears to be supported by the {@code DataStore}.
-     * Returning {@code TRUE} from this method does not guarantee that reading or writing will succeed,
+     * Indicates if the given storage appears to be supported by the {@code DataStore}s created by this provider.
+     * The most typical return values are:
+     *
+     * <ul>
+     *   <li>{@link ProbeResult#SUPPORTED} if the {@code DataStore}s created by this provider
+     *       can open the given storage.</li>
+     *   <li>{@link ProbeResult#UNSUPPORTED_STORAGE} if the given storage does not appear to be in a format
+     *       supported by this {@code DataStoreProvider}.</li>
+     * </ul>
+     *
+     * Note that the {@code SUPPORTED} value does not guarantee that reading or writing will succeed,
      * only that there appears to be a reasonable chance of success based on a brief inspection of the
      * {@linkplain StorageConnector#getStorage() storage object} or contents.
      *
-     * <p>Implementations will typically check the first bytes of the stream for a "magic number"
-     * associated with the format, as in the following example:</p>
+     * <p>Implementors are responsible for restoring the input to its original stream position on return of this method.
+     * Implementors can use a mark/reset pair for this purpose. Marks are available as
+     * {@link java.nio.ByteBuffer#mark()}, {@link java.io.InputStream#mark(int)} and
+     * {@link javax.imageio.stream.ImageInputStream#mark()}.</p>
+     *
+     * {@section Implementation example}
+     * Implementations will typically check the first bytes of the stream for a "magic number" associated
+     * with the format, as in the following example:
      *
      * {@preformat java
-     *     final ByteBuffer buffer = storage.getStorageAs(ByteBuffer.class);
-     *     if (buffer == null) {
-     *         // If StorageConnector can not provide a ByteBuffer, then the storage is probably
-     *         // not a File, URL, URI, InputStream neither a ReadableChannel. In this example,
-     *         // our provider can not handle such unknown source.
-     *         return Boolean.FALSE;
-     *     }
-     *     if (buffer.remaining() < Integer.SIZE / Byte.SIZE) {
-     *         // If the buffer does not contain enough bytes for the 'int' type, this is not necessarily
-     *         // because the file is truncated. It may be because the data were not yet available at the
-     *         // time this method has been invoked. Returning 'null' means "don't know".
-     *         return null;
+     *     public ProbeResult probeContent(StorageConnector storage) throws DataStoreException {
+     *         final ByteBuffer buffer = storage.getStorageAs(ByteBuffer.class);
+     *         if (buffer == null) {
+     *             // If StorageConnector can not provide a ByteBuffer, then the storage is
+     *             // probably not a File, URL, URI, InputStream neither a ReadableChannel.
+     *             return ProbeResult.UNSUPPORTED_STORAGE;
+     *         }
+     *         if (buffer.remaining() < Integer.SIZE / Byte.SIZE) {
+     *             // If the buffer does not contain enough bytes for the integer type, this is not
+     *             // necessarily because the file is truncated. It may be because the data were not
+     *             // yet available at the time this method has been invoked.
+     *             return ProbeResult.INSUFFICIENT_BYTES;
+     *         }
+     *         if (buffer.getInt(buffer.position()) != MAGIC_NUMBER) {
+     *             // We used ByteBuffer.getInt(int) instead than ByteBuffer.getInt() above
+     *             // in order to keep the buffer position unchanged after this method call.
+     *             return ProbeResult.UNSUPPORTED_STORAGE;
+     *         }
+     *         return ProbeResult.SUPPORTED;
      *     }
-     *     // Use ByteBuffer.getInt(int) instead than ByteBuffer.getInt() in order to keep buffer position
-     *     // unchanged after this method call.
-     *     return buffer.getInt(buffer.position()) == MAGIC_NUMBER;
      * }
      *
-     * Implementors are responsible for restoring the input to its original stream position on return of this method.
-     * Implementors can use a mark/reset pair for this purpose. Marks are available as
-     * {@link java.nio.ByteBuffer#mark()}, {@link java.io.InputStream#mark(int)} and
-     * {@link javax.imageio.stream.ImageInputStream#mark()}.
-     *
      * @param  storage Information about the storage (URL, stream, JDBC connection, <i>etc</i>).
-     * @return {@link Boolean#TRUE} if the given storage seems to be usable by the {@code DataStore} instances
-     *         create by this provider, {@link Boolean#FALSE} if the {@code DataStore} will not be able to use
-     *         the given storage, or {@code null} if this method does not have enough information.
+     * @return {@link ProbeResult#SUPPORTED} if the given storage seems to be readable by the {@code DataStore}
+     *         instances created by this provider.
      * @throws DataStoreException if an I/O or SQL error occurred. The error shall be unrelated to the logical
      *         structure of the storage.
      */
-    public abstract Boolean canOpen(StorageConnector storage) throws DataStoreException;
+    public abstract ProbeResult probeContent(StorageConnector storage) throws DataStoreException;
 
     /**
      * Returns a data store implementation associated with this provider.
      *
-     * <p><b>Implementation note:</b>
+     * {@section Implementation note}
      * Implementors shall invoke {@link StorageConnector#closeAllExcept(Object)} after {@code DataStore}
-     * creation, keeping open only the needed resource.</p>
+     * creation, keeping open only the needed resource.
      *
      * @param  storage Information about the storage (URL, stream, JDBC connection, <i>etc</i>).
      * @return A data store implementation associated with this provider for the given storage.
-     * @throws DataStoreException if an error occurred while creating the data store instance.
+     * @throws IllegalArgumentException If the set contains an invalid combination of options.
+     * @throws DataStoreException If an error occurred while creating the data store instance.
+     *
+     * @see DataStores#open(Object)
      */
     public abstract DataStore open(StorageConnector storage) throws DataStoreException;
+
+    /**
+     * Returns {@code TRUE} if the given storage appears to be supported by the {@code DataStore}.
+     *
+     * @param  storage Information about the storage (URL, stream, JDBC connection, <i>etc</i>).
+     * @return {@link Boolean#TRUE} if the given storage seems to be usable by the {@code DataStore} instances
+     *         create by this provider, {@link Boolean#FALSE} if the {@code DataStore} will not be able to use
+     *         the given storage, or {@code null} if this method does not have enough information.
+     * @throws DataStoreException if an I/O or SQL error occurred. The error shall be unrelated to the logical
+     *         structure of the storage.
+     *
+     * @deprecated Replaced by {@link #probeContent(StorageConnector)}.
+     */
+    @Deprecated
+    public Boolean canOpen(final StorageConnector storage) throws DataStoreException {
+        final ProbeResult probe = probeContent(storage);
+        if (ProbeResult.INSUFFICIENT_BYTES.equals(probe) || ProbeResult.UNDETERMINED.equals(probe)) {
+            return null;
+        }
+        return probe.isSupported();
+    }
 }

Modified: sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java [UTF-8] Sun Aug 25 15:49:51 2013
@@ -17,13 +17,20 @@
 package org.apache.sis.storage;
 
 import java.util.Map;
+import java.util.Queue;
+import java.util.Iterator;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.IdentityHashMap;
 import java.util.ConcurrentModificationException;
+import java.io.Reader;
 import java.io.DataInput;
+import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.Serializable;
 import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
 import java.nio.channels.ReadableByteChannel;
 import javax.imageio.ImageIO;
 import javax.imageio.stream.ImageInputStream;
@@ -31,7 +38,6 @@ import java.sql.Connection;
 import javax.sql.DataSource;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
-import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.storage.IOUtilities;
@@ -48,10 +54,10 @@ import org.apache.sis.internal.jdk7.JDK7
  * {@code StorageConnector} 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} or file or a directory in a file system.</li>
+ *   <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} or a {@link DataInput}.</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>
  * </ul>
@@ -85,6 +91,12 @@ public class StorageConnector implements
     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;
+
+    /**
      * The input/output object given at construction time.
      *
      * @see #getStorage()
@@ -119,6 +131,12 @@ public class StorageConnector implements
      *   <li>{@link ImageInputStream}:
      *       Same as {@code DataInput} if it can be casted, or {@code null} otherwise.</li>
      *
+     *   <li>{@link InputStream}:
+     *       If not explicitely provided, this is a wrapper around the above {@link ImageInputStream}.</li>
+     *
+     *   <li>{@link Reader}:
+     *       If not explicitely provided, this is a wrapper around the above {@link InputStream}.</li>
+     *
      *   <li>{@link Connection}:
      *       The storage object as a JDBC connection.</li>
      * </ul>
@@ -132,6 +150,17 @@ public class StorageConnector implements
     private transient Map<Class<?>, Object> views;
 
     /**
+     * Objects which will need to be closed by the {@link #closeAllExcept(Object)} method.
+     * For each (<var>key</var>, <var>value</var>) entry, if the object to close (the key)
+     * is a wrapper around an other object (e.g. an {@link InputStreamReader} wrapping an
+     * {@link InputStream}), then the value is the other object.
+     *
+     * @see #addViewToClose(Object, Object)
+     * @see #closeAllExcept(Object)
+     */
+    private transient Map<Object, Object> viewsToClose;
+
+    /**
      * The options, created only when first needed.
      *
      * @see #getOption(OptionKey)
@@ -163,11 +192,13 @@ public class StorageConnector implements
     }
 
     /**
-     * Sets the option value for the given key. The default implementation recognizes the given options:
+     * Sets the option value for the given key. The default implementation recognizes the following options:
      *
      * <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#BYTE_BUFFER} for allowing users to control the byte buffer to be created.</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.
@@ -197,7 +228,7 @@ public class StorageConnector implements
      *
      * <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 Path#getFileName()}.</li>
+     *       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.
@@ -262,12 +293,12 @@ public class StorageConnector implements
      *   </li>
      *   <li>{@link DataInput}:
      *     <ul>
-     *       <li>If the {@linkplain #getStorage() storage} object is already an instance of {@link DataInput}
+     *       <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 java.io.InputStream} or
+     *           {@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>
      *
@@ -279,7 +310,30 @@ public class StorageConnector implements
      *   </li>
      *   <li>{@link ImageInputStream}:
      *     <ul>
-     *       <li>If the {@code DataInput} computed above can be casted to {@code null}, returns it.</li>
+     *       <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>
@@ -298,9 +352,9 @@ public class StorageConnector implements
      * </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#canOpen(StorageConnector)} methods shall not close the stream or
-     * database connection returned by this method. In addition, those {@code canOpen(StorageConnector)}
+     * 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.
@@ -366,14 +420,17 @@ public class StorageConnector implements
 
     /**
      * Creates a view for the input as a {@link ChannelDataInput} if possible.
+     * If the view can not be created, remember that fact in order to avoid new attempts.
      *
      * @param  asImageInputStream If the {@code ChannelDataInput} needs to be {@link ChannelImageInputStream} subclass.
      * @throws IOException If an error occurred while opening a channel for the input.
      */
     private void createChannelDataInput(final boolean asImageInputStream) throws IOException {
-        final ReadableByteChannel channel = IOUtilities.open(storage, getOption(OptionKey.URL_ENCODING));
+        final ReadableByteChannel channel = IOUtilities.open(storage,
+                getOption(OptionKey.URL_ENCODING), getOption(OptionKey.OPEN_OPTIONS));
         ChannelDataInput asDataInput = null;
         if (channel != null) {
+            addViewToClose(channel, storage);
             ByteBuffer buffer = getOption(OptionKey.BYTE_BUFFER);
             if (buffer == null) {
                 buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
@@ -386,6 +443,7 @@ public class StorageConnector implements
             } else {
                 asDataInput = new ChannelDataInput(name, channel, buffer, false);
             }
+            addViewToClose(asDataInput, channel);
         }
         addView(ChannelDataInput.class, asDataInput);
     }
@@ -414,27 +472,34 @@ public class StorageConnector implements
             final ChannelDataInput c = getView(ChannelDataInput.class);
             if (c == null) {
                 asDataInput = ImageIO.createImageInputStream(storage);
+                addViewToClose(asDataInput, storage);
             } else if (c instanceof DataInput) {
                 asDataInput = (DataInput) c;
+                // No call to 'addViewToClose' because the instance already exists.
             } else {
                 asDataInput = new ChannelImageInputStream(c);
-                if (views.put(ChannelDataInput.class, asDataInput) != c) {
+                if (views.put(ChannelDataInput.class, asDataInput) != c) { // Replace the previous instance.
                     throw new ConcurrentModificationException();
                 }
+                addViewToClose(asDataInput, c.channel);
             }
         }
         addView(DataInput.class, asDataInput);
     }
 
     /**
-     * If an {@link ImageInputStream} has been created without buffer, read an arbitrary amount of bytes now so
-     * we can provide a {@link ByteBuffer} for users who want it. We read only a small amount of bytes because,
-     * at the contrary of the buffer created in {@link #createChannelAndBuffer(boolean)}, the buffer created
-     * here is unlikely to be used for the reading process after the recognition of the file format.
+     * 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. This amount is not sufficient, it can be increased by a call
+     * to {@link #prefetch()}.
      *
      * @throws IOException If an error occurred while opening a stream for the input.
      */
     private void 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.
+         */
         if (!views.containsKey(ChannelDataInput.class)) {
             createChannelDataInput(false);
         }
@@ -443,15 +508,22 @@ public class StorageConnector implements
         if (c != null) {
             asByteBuffer = c.buffer.asReadOnlyBuffer();
         } else {
+            /*
+             * If no ChannelDataInput has been create 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[256];
+                final byte[] buffer = new byte[MINIMAL_BUFFER_SIZE];
                 final int n = in.read(buffer);
                 in.reset();
                 if (n >= 1) {
-                    asByteBuffer = ByteBuffer.wrap(ArraysExt.resize(buffer, n))
-                            .asReadOnlyBuffer().order(in.getByteOrder());
+                    asByteBuffer = ByteBuffer.wrap(buffer).order(in.getByteOrder());
+                    asByteBuffer.limit(n);
+                    // Can't invoke asReadOnly() because 'prefetch()' need to be able to write in it.
                 }
             }
         }
@@ -459,6 +531,46 @@ public class StorageConnector implements
     }
 
     /**
+     * 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 {
+            final ChannelDataInput c = getView(ChannelDataInput.class);
+            if (c != null) {
+                return c.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.
+             */
+            final ImageInputStream input = getView(ImageInputStream.class);
+            if (input != null) {
+                final ByteBuffer buffer = getView(ByteBuffer.class);
+                if (buffer != null) {
+                    final int p = buffer.limit();
+                    final int n = input.read(buffer.array(), p, buffer.capacity() - p);
+                    if (n >= 0) {
+                        buffer.limit(p + n);
+                        return true;
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new DataStoreException(Errors.format(Errors.Keys.CanNotRead_1, getStorageName()), e);
+        }
+        return false;
+    }
+
+    /**
      * Creates a storage view of the given type if possible, or returns {@code null} otherwise.
      * This method is invoked by {@link #getStorageAs(Class)} when first needed, and the result is cached.
      *
@@ -475,14 +587,58 @@ public class StorageConnector implements
         if (type == Connection.class) {
             if (storage instanceof Connection) {
                 return storage;
-            } else if (storage instanceof DataSource) {
-                return ((DataSource) storage).getConnection();
             }
+            if (storage instanceof DataSource) {
+                final Connection c = ((DataSource) storage).getConnection();
+                addViewToClose(c, storage);
+                return c;
+            }
+            return null;
         }
         if (type == ImageInputStream.class) {
             final DataInput input = getStorageAs(DataInput.class);
             return (input instanceof ImageInputStream) ? input : null;
         }
+        if (type == InputStream.class) {
+            if (storage instanceof InputStream) {
+                return storage;
+            }
+            final DataInput input = getStorageAs(DataInput.class);
+            if (input instanceof InputStream) {
+                return (InputStream) input;
+            }
+            if (input instanceof ImageInputStream) {
+                final InputStream c = new InputStreamAdapter((ImageInputStream) input);
+                addViewToClose(c, input);
+                return c;
+            }
+            return null;
+        }
+        if (type == Reader.class) {
+            if (storage instanceof Reader) {
+                return storage;
+            }
+            final InputStream input = getStorageAs(InputStream.class);
+            if (input != null) {
+                final Charset encoding = getOption(OptionKey.ENCODING);
+                final Reader c = (encoding != null) ? new InputStreamReader(input, encoding)
+                                                    : new InputStreamReader(input);
+                /*
+                 * Current implementation does not wrap the above Reader in a BufferedReader because:
+                 *
+                 * 1) InputStreamReader already uses a buffer internally.
+                 * 2) InputStreamReader does not support mark/reset, which is a desired limitation for now.
+                 *    This is because reseting the Reader would not reset the underlying InputStream, which
+                 *    would cause other DataStoreProvider.probeContent(…) methods to fail if they try to use
+                 *    the InputStream. For now we let the InputStreamReader.mark() to throws an IOException,
+                 *    but we may need to provide our own subclass of BufferedReader in a future SIS version
+                 *    if mark/reset support is needed here.
+                 */
+                addViewToClose(c, input);
+                return c;
+            }
+            return null;
+        }
         throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownType_1, type));
     }
 
@@ -512,6 +668,23 @@ public class StorageConnector implements
     }
 
     /**
+     * Declares that the given {@code input} will need to be closed by the {@link #closeAllExcept(Object)} method.
+     * The {@code input} argument is always a new instance wrapping, directly or indirectly, the {@link #storage}.
+     * Callers must specify the wrapped object in the {@code delegate} argument.
+     *
+     * @param input    The newly created object which will need to be closed.
+     * @param delegate The object wrapped by the given {@code input}.
+     */
+    private void addViewToClose(final Object input, final Object delegate) {
+        if (viewsToClose == null) {
+            viewsToClose = new IdentityHashMap<Object,Object>(4);
+        }
+        if (viewsToClose.put(input, delegate) != null) {
+            throw new AssertionError(input);
+        }
+    }
+
+    /**
      * 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()
@@ -530,37 +703,86 @@ public class StorageConnector implements
      * @see DataStoreProvider#open(StorageConnector)
      */
     public void closeAllExcept(final Object view) throws DataStoreException {
+        final Map<Object,Object> toClose = viewsToClose;
+        viewsToClose = Collections.emptyMap();
+        views        = Collections.emptyMap();
+        if (toClose == null) {
+            if (storage != view && JDK7.isAutoCloseable(storage)) try {
+                JDK7.close(storage);
+            } catch (Exception e) {
+                throw new DataStoreException(e);
+            }
+            return;
+        }
         /*
-         * Need a set of objects to close without duplicated values. In particular, the value for
-         * DataInput and ImageInputStream are often the same instance. We must avoid duplicated
-         * values because ImageInputStream.close() is not indempotent.
+         * 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).
+         *
+         * The set of objects to close will be the keys of the 'viewsToClose' map. It can not be the values of the
+         * 'views' map.
          */
-        final Map<Object,Object> toClose = new IdentityHashMap<Object,Object>(4);
-        for (final Object value : views.values()) {
-            if (JDK7.isAutoCloseable(value)) {
-                toClose.put(value, null);
-            }
+        toClose.put(storage, null);
+        if (view != null) {
+            /*
+             * 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.
+             */
+            final Queue<Object> deferred = new LinkedList<Object>();
+            Object doNotClose = view;
+            do {
+                final Iterator<Map.Entry<Object,Object>> it = toClose.entrySet().iterator();
+                while (it.hasNext()) {
+                    final Map.Entry<Object,Object> entry = it.next();
+                    if (entry.getValue() == doNotClose) {
+                        deferred.add(entry.getKey());
+                        it.remove();
+                    }
+                }
+                doNotClose = deferred.poll();
+            } while (doNotClose != null);
         }
-        toClose.remove(view);
-        toClose.remove(storage);
-        try {
-            if (!toClose.isEmpty()) {
-                for (final Object value : toClose.keySet()) {
-                    JDK7.close(value);
+        /*
+         * Remove the view to not close. If that view is a wrapper for an other object, do not close the
+         * wrapped object neither. Proceed the dependency chain up to the original 'storage' object.
+         */
+        for (Object doNotClose = view; doNotClose != null;) {
+            doNotClose = toClose.remove(doNotClose);
+        }
+        /*
+         * Remove all wrapped objects. After this loop, only the "top level" objects should remain
+         * (typically only one object). This block is needed because of the "AutoCloseable.close()
+         * is not idempotent" issue, otherwise we could have omitted it.
+         */
+        for (final Object delegate : toClose.values().toArray()) { // 'toArray()' is for avoiding ConcurrentModificationException.
+            toClose.remove(delegate);
+        }
+        /*
+         * Now close all remaining items. If an exception occurs, we will propagate it only after we are
+         * done closing all items.
+         */
+        DataStoreException failure = null;
+        for (final Object c : toClose.keySet()) {
+            if (JDK7.isAutoCloseable(c)) try {
+                JDK7.close(c);
+            } catch (Exception e) {
+                if (failure == null) {
+                    failure = new DataStoreException(e);
+                } else {
+                    // failure.addSuppressed(e) on the JDK7 branch.
                 }
-            } else if (view == null && JDK7.isAutoCloseable(storage)) {
-                /*
-                 * Close only if we didn't closed a view because closing an input stream view
-                 * automatically close the 'storage' if the former is a wrapper for the later.
-                 * Since AutoCloseable.close() is not guaranteed to be indempotent, we should
-                 * avoid to call it (indirectly) twice.
-                 */
-                JDK7.close(storage);
             }
-        } catch (Exception e) {
-            throw new DataStoreException(e);
-        } finally {
-            views = Collections.emptyMap();
+        }
+        if (failure != null) {
+            throw failure;
         }
     }
 

Modified: sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java [UTF-8] Sun Aug 25 15:49:51 2013
@@ -19,24 +19,15 @@
  * {@linkplain org.apache.sis.storage.DataStore Data store} base types for retrieving and saving geospatial data
  * in various storage formats.
  *
- * <p>Different {@code DataStore} implementations will want different kind of input/output objects. Some examples are
- * {@link java.lang.String}, {@link java.nio.file.Path}, {@link java.io.File}, {@link java.net.URI}, {@link java.net.URL},
- * {@link java.io.InputStream}, {@link javax.imageio.stream.ImageInputStream}, {@link java.nio.channels.ReadableChannel},
- * JDBC {@link java.sql.Connection} or {@link javax.sql.DataSource}, or even
- * datastore-specific objects like {@link ucar.nc2.NetcdfFile}.
- * Because of this variety, SIS does not know which kind of object to accept before the appropriate {@code DataStore}
- * instance has been found. For this reason, storages are represented by arbitrary {@link java.lang.Object} encapsulated
- * in {@link org.apache.sis.storage.StorageConnector}. The later can open the object in various ways, for example
- * as {@link java.io.DataInput} or as {@link java.nio.ByteBuffer}, depending on {@code DataStore needs}.
- * Future versions may contain additional information like login/password.</p>
- *
- * <p>{@code StorageConnector} is used only for the "discovery" phase, and discarded once the actual
- * {@code DataStore} instance has been created.</p>
+ * <p>{@code DataStore} provides the methods for reading or writing geospatial data in a given storage.
+ * A storage may be a file, a directory, a connection to a database or any other implementation specific mechanism.
+ * Suitable {@code DataStore} implementation for a given storage can be discovered and opened by the static methods
+ * provided in {@link org.apache.sis.storage.DataStores}.</p>
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.10)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 package org.apache.sis.storage;

Modified: sis/branches/Shapefile/storage/sis-storage/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/site/apt/index.apt?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/site/apt/index.apt [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/site/apt/index.apt [UTF-8] Sun Aug 25 15:49:51 2013
@@ -1,3 +1,22 @@
+~~
+~~ 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.
+~~
+
                              ---------------------------
                                Apache SIS storage base
                              ---------------------------

Modified: sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java [UTF-8] Sun Aug 25 15:49:51 2013
@@ -22,7 +22,6 @@ import java.io.DataInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -36,7 +35,7 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.07)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public final strictfp class ChannelDataInputTest extends TestCase {
@@ -46,6 +45,20 @@ public final strictfp class ChannelDataI
     private static final int ARRAY_MAX_SIZE = 256;
 
     /**
+     * Creates an array filled with random values.
+     *
+     * @param length The length of the array to create.
+     * @param random The random number generator to use.
+     */
+    static byte[] createRandomArray(final int length, final Random random) {
+        final byte[] array = new byte[length];
+        for (int i=0; i<length; i++) {
+            array[i] = (byte) random.nextInt(256);
+        }
+        return array;
+    }
+
+    /**
      * Fills a buffer with random data and compare the result with a standard image input stream.
      * We allocate a small buffer for the {@code ChannelDataInput} in order to force frequent
      * interactions between the buffer and the channel.
@@ -59,7 +72,7 @@ public final strictfp class ChannelDataI
         compareStreamToBuffer(random, array.length,
                 new DataInputStream(new ByteArrayInputStream(array)),
                 new ChannelDataInput("testAllReadMethods",
-                    Channels.newChannel(new ByteArrayInputStream(array)),
+                    new DripByteChannel(array, random, 1, 1024),
                     ByteBuffer.allocate(random.nextInt(ARRAY_MAX_SIZE / 4) + (Double.SIZE / Byte.SIZE)), false));
     }
 
@@ -149,11 +162,12 @@ public final strictfp class ChannelDataI
      */
     @Test
     public void testReadString() throws IOException {
+        final Random random   = TestUtilities.createRandomNumberGenerator("testReadString");
         final String expected = "お元気ですか";
-        final byte[] array = expected.getBytes("UTF-8");
+        final byte[] array    = expected.getBytes("UTF-8");
         assertEquals(expected.length()*3, array.length); // Sanity check.
         final ChannelDataInput input = new ChannelDataInput("testReadString",
-                Channels.newChannel(new ByteArrayInputStream(array)),
+                new DripByteChannel(array, random, 1, 32),
                 ByteBuffer.allocate(array.length + 4), false);
         assertEquals(expected, input.readString(array.length, "UTF-8"));
         assertFalse(input.buffer.hasRemaining());
@@ -170,13 +184,13 @@ public final strictfp class ChannelDataI
         final Random random = TestUtilities.createRandomNumberGenerator("testSeekOnForwardOnlyChannel");
         int length = random.nextInt(2048) + 1024;
         final byte[] array = createRandomArray(length, random);
-        length -= (Long.SIZE / Byte.SIZE) - 1; // Safety against buffer underflow.
+        length -= (Long.SIZE / Byte.SIZE); // Safety against buffer underflow.
         final ByteBuffer buffer = ByteBuffer.wrap(array);
         final ChannelDataInput input = new ChannelDataInput("testSeekOnForwardOnlyChannel",
-                Channels.newChannel(new ByteArrayInputStream(array)),
+                new DripByteChannel(array, random, 1, 2048),
                 ByteBuffer.allocate(random.nextInt(64) + 16), false);
         int position = 0;
-        while (position <= length) {
+        while (position < length) {
             input.seek(position);
             assertEquals("getStreamPosition()", position, input.getStreamPosition());
             assertEquals(buffer.getLong(position), input.readLong());
@@ -185,16 +199,46 @@ public final strictfp class ChannelDataI
     }
 
     /**
-     * Creates an array filled with random values.
+     * Tests {@link ChannelDataInput#prefetch()}.
      *
-     * @param length The length of the array to create.
-     * @param random The random number generator to use.
+     * @throws IOException Should never happen.
      */
-    static byte[] createRandomArray(final int length, final Random random) {
-        final byte[] array = new byte[length];
-        for (int i=0; i<length; i++) {
-            array[i] = (byte) random.nextInt(256);
+    @Test
+    public void testPrefetch() throws IOException {
+        final Random     random = TestUtilities.createRandomNumberGenerator("testPrefetch");
+        final int        length = random.nextInt(256) + 128;
+        final byte[]     array  = createRandomArray(length, random);
+        final ByteBuffer buffer = ByteBuffer.allocate(random.nextInt(64) + 16);
+        final ChannelDataInput input = new ChannelDataInput("testPrefetch",
+                new DripByteChannel(array, random, 1, 64), buffer, false);
+        int position = 0;
+        while (position != length) {
+            if (random.nextBoolean()) {
+                assertEquals(array[position++], input.readByte());
+            }
+            /*
+             * Prefetch a random amount of bytes and verifies the buffer status.
+             */
+            final int p = buffer.position();
+            final int m = buffer.limit();
+            final int n = input.prefetch();
+            assertEquals("Position shall be unchanged.", p, buffer.position());
+            final int limit = buffer.limit();
+            if (n >= 0) {
+                // Usual case.
+                assertTrue("Limit shall be increased.", limit > m);
+            } else {
+                // Buffer is full or channel reached the end of stream.
+                assertEquals("Limit shall be unchanged", m, limit);
+            }
+            /*
+             * Compare the buffer content with the original data array. The comparison starts
+             * from the buffer begining, in order to ensure that previous data are unchanged.
+             */
+            final int offset = position - buffer.position();
+            for (int i=0; i<limit; i++) {
+                assertEquals(array[offset + i], buffer.get(i));
+            }
         }
-        return array;
     }
 }

Modified: sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java [UTF-8] Sun Aug 25 15:49:51 2013
@@ -17,11 +17,15 @@
 package org.apache.sis.storage;
 
 import java.io.DataInput;
+import java.io.InputStream;
+import java.io.Reader;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
 import javax.imageio.ImageIO;
 import javax.imageio.stream.ImageInputStream;
+import java.sql.Connection;
+import org.apache.sis.setup.OptionKey;
 import org.apache.sis.internal.storage.ChannelDataInput;
 import org.apache.sis.internal.storage.ChannelImageInputStream;
 import org.apache.sis.test.DependsOnMethod;
@@ -37,7 +41,7 @@ import static org.opengis.test.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.4
  * @module
  */
 @DependsOn(org.apache.sis.internal.storage.ChannelImageInputStreamTest.class)
@@ -56,7 +60,9 @@ public final strictfp class StorageConne
         final String name = c.getSimpleName() + ".class";
         final Object storage = asStream ? c.getResourceAsStream(name) : c.getResource(name);
         assertNotNull(storage);
-        return new StorageConnector(storage);
+        final StorageConnector connector = new StorageConnector(storage);
+        connector.setOption(OptionKey.URL_ENCODING, "UTF-8");
+        return connector;
     }
 
     /**
@@ -149,6 +155,50 @@ public final strictfp class StorageConne
     }
 
     /**
+     * Tests the {@link StorageConnector#getStorageAs(Class)} method for the {@link InputStream} type.
+     *
+     * @throws DataStoreException Should never happen.
+     * @throws IOException If an error occurred while reading the test file.
+     */
+    @Test
+    @DependsOnMethod("testGetAsImageInputStream")
+    public void testGetAsInputStream() throws DataStoreException, IOException {
+        StorageConnector connection = create(true);
+        InputStream in = connection.getStorageAs(InputStream.class);
+        assertSame(connection.getStorage(), in);
+        connection.closeAllExcept(null);
+
+        connection = create(false);
+        in = connection.getStorageAs(InputStream.class);
+        assertNotSame(connection.getStorage(), in);
+        assertSame("Expected cached value.", in, connection.getStorageAs(InputStream.class));
+        assertInstanceOf("Expected Channel backend", InputStreamAdapter.class, in);
+        assertInstanceOf("Expected Channel backend", ChannelImageInputStream.class, ((InputStreamAdapter) in).input);
+        assertSame(((InputStreamAdapter) in).input, connection.getStorageAs(DataInput.class));
+        assertSame(((InputStreamAdapter) in).input, connection.getStorageAs(ImageInputStream.class));
+
+        final ReadableByteChannel channel = ((ChannelImageInputStream) ((InputStreamAdapter) in).input).channel;
+        assertTrue(channel.isOpen());
+        connection.closeAllExcept(null);
+        assertFalse(channel.isOpen());
+    }
+
+    /**
+     * Tests the {@link StorageConnector#getStorageAs(Class)} method for the {@link Reader} type.
+     *
+     * @throws DataStoreException Should never happen.
+     * @throws IOException If an error occurred while reading the test file.
+     */
+    @Test
+    @DependsOnMethod("testGetAsInputStream")
+    public void testGetAsReader() throws DataStoreException, IOException {
+        StorageConnector connection = create(true);
+        final Reader in = connection.getStorageAs(Reader.class);
+        assertSame("Expected cached value.", in, connection.getStorageAs(Reader.class));
+        connection.closeAllExcept(null);
+    }
+
+    /**
      * Tests the {@link StorageConnector#getStorageAs(Class)} method for the {@link ChannelDataInput} type.
      * The initial value should not be an instance of {@link ChannelImageInputStream} in order to avoid initializing
      * the Image I/O classes. However after a call to {@code getStorageAt(ChannelImageInputStream.class)}, the type
@@ -213,12 +263,24 @@ public final strictfp class StorageConne
 
         final ByteBuffer buffer = connection.getStorageAs(ByteBuffer.class);
         assertNotNull("getStorageAs(ByteBuffer.class)", buffer);
-        assertTrue(buffer.capacity() < StorageConnector.DEFAULT_BUFFER_SIZE);
+        assertEquals(StorageConnector.MINIMAL_BUFFER_SIZE, buffer.capacity());
         assertEquals(MAGIC_NUMBER, buffer.getInt());
         connection.closeAllExcept(null);
     }
 
     /**
+     * Tests the {@link StorageConnector#getStorageAs(Class)} method for the {@link Connection} type.
+     *
+     * @throws DataStoreException Should never happen.
+     * @throws IOException Should never happen.
+     */
+    public void testGetAsConnection() throws DataStoreException, IOException {
+        final StorageConnector connection = create(false);
+        assertNull(connection.getStorageAs(Connection.class));
+        connection.closeAllExcept(null);
+    }
+
+    /**
      * Tests the {@link StorageConnector#closeAllExcept(Object)} method.
      *
      * @throws DataStoreException Should never happen.

Modified: sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java [UTF-8] (original)
+++ sis/branches/Shapefile/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java [UTF-8] Sun Aug 25 15:49:51 2013
@@ -26,14 +26,19 @@ import org.junit.BeforeClass;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.4
  * @module
  */
 @Suite.SuiteClasses({
     org.apache.sis.internal.storage.IOUtilitiesTest.class,
     org.apache.sis.internal.storage.ChannelDataInputTest.class,
     org.apache.sis.internal.storage.ChannelImageInputStreamTest.class,
+    org.apache.sis.storage.ProbeResultTest.class,
     org.apache.sis.storage.StorageConnectorTest.class,
+    org.apache.sis.internal.storage.xml.MimeTypeDetectorTest.class,
+    org.apache.sis.internal.storage.xml.XMLStoreProviderTest.class,
+    org.apache.sis.internal.storage.xml.XMLStoreTest.class,
+    org.apache.sis.storage.DataStoresTest.class,
     org.apache.sis.index.GeoHashCoderTest.class
 })
 public final strictfp class StorageTestSuite extends TestSuite {

Modified: sis/branches/Shapefile/storage/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/sis/branches/Shapefile/storage/src/site/apt/index.apt?rev=1517321&r1=1517320&r2=1517321&view=diff
==============================================================================
--- sis/branches/Shapefile/storage/src/site/apt/index.apt [UTF-8] (original)
+++ sis/branches/Shapefile/storage/src/site/apt/index.apt [UTF-8] Sun Aug 25 15:49:51 2013
@@ -1,3 +1,22 @@
+~~
+~~ 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.
+~~
+
                              ------------------------------
                                Apache SIS storage modules
                              ------------------------------



Mime
View raw message