Author: desruisseaux
Date: Mon Aug  5 17:47:23 2013
New Revision: 1510624

URL: http://svn.apache.org/r1510624
Log:
Prefetch more bytes when the ByteBuffer doesn't contain enough bytes for 
allowing us to recognize the format.
We try to prefetch more bytes only if necessary in order to avoid latency on 
network connections.

Added:
    
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/DripByteChannel.java
   (with props)
Modified:
    
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Static.java
    
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    
sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
    
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelDataInput.java
    
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
    
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
    
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/ProbeResult.java
    
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
    
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java

Modified: 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Static.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Static.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Static.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Static.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -58,6 +58,8 @@ package org.apache.sis.util;
  * <tr><th colspan="2" class="hsep">Input / Output (including CRS, XML, 
images)</th></tr>
  * <tr><td>{@link org.apache.sis.io.IO}</td>
  *     <td>Methods working on {@link Appendable} instances.</td></tr>
+ * <tr><td>{@link org.apache.sis.storage.DataStores}</td>
+ *     <td>Read or write geospatial data in various backends.</td></tr>
  * <tr><td>{@link org.apache.sis.xml.XML}</td>
  *     <td>Marshal or unmarshal ISO 19115 objects.</td></tr>
  * <tr><td>{@link org.apache.sis.xml.Namespaces}</td>

Modified: 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -99,6 +99,11 @@ public final class Errors extends Indexe
         public static final int CanNotParseFile_2 = 79;
 
         /**
+         * Can not read “{0}”.
+         */
+        public static final int CanNotRead_1 = 108;
+
+        /**
          * Can not set a value for property “{0}”.
          */
         public static final int CanNotSetPropertyValue_1 = 75;

Modified: 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
 [ISO-8859-1] (original)
+++ 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
 [ISO-8859-1] Mon Aug  5 17:47:23 2013
@@ -31,6 +31,7 @@ CanNotComputeDerivative         = Can no
 CanNotInstantiate_1             = Can not instantiate an object of type 
\u2018{0}\u2019.
 CanNotOpen_1                    = Can not open \u201c{0}\u201d.
 CanNotParseFile_2               = Can not parse \u201c{1}\u201d as a file in 
the {0} format.
+CanNotRead_1                    = Can not read \u201c{0}\u201d.
 CanNotSetPropertyValue_1        = Can not set a value for property 
\u201c{0}\u201d.
 ClassNotFinal_1                 = Class \u2018{0}\u2019 is not final.
 CloneNotSupported_1             = Can not clone an object of type 
\u2018{0}\u2019.

Modified: 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
 [ISO-8859-1] (original)
+++ 
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
 [ISO-8859-1] Mon Aug  5 17:47:23 2013
@@ -21,6 +21,7 @@ CanNotComputeDerivative         = La d\u
 CanNotInstantiate_1             = Ne peut pas cr\u00e9er un objet de type 
\u2018{0}\u2019.
 CanNotOpen_1                    = Ne peut pas ouvrir \u201c{0}\u201d.
 CanNotParseFile_2               = Ne peut pas lire \u201c{1}\u201d comme un 
fichier au format {0}.
+CanNotRead_1                    = Ne peut pas lire \u201c{0}\u201d.
 CanNotSetPropertyValue_1        = Ne peut pas d\u00e9finir une valeur pour la 
propri\u00e9t\u00e9 \u201c{0}\u201d.
 ClassNotFinal_1                 = La classe \u2018{0}\u2019 n\u2019est pas 
finale.
 CloneNotSupported_1             = Un objet de type \u2018{0}\u2019 ne peut pas 
\u00eatre clon\u00e9.

Modified: 
sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -116,7 +116,7 @@ public class NetcdfStoreProvider extends
         final ByteBuffer buffer = storage.getStorageAs(ByteBuffer.class);
         if (buffer != null) {
             if (buffer.remaining() < Integer.SIZE / Byte.SIZE) {
-                return ProbeResult.UNDETERMINED;
+                return ProbeResult.INSUFFICIENT_BYTES;
             }
             final int header = buffer.getInt(buffer.position());
             if ((header & 0xFFFFFF00) == ChannelDecoder.MAGIC_NUMBER) {
@@ -131,7 +131,7 @@ public class NetcdfStoreProvider extends
             ensureInitialized();
             final Method method = canOpenFromPath;
             if (method != null) try {
-                return ((Boolean) method.invoke(null, path)) ? 
ProbeResult.SUPPORTED : ProbeResult.UNKNOWN_FORMAT;
+                return ((Boolean) method.invoke(null, path)) ? 
ProbeResult.SUPPORTED : ProbeResult.UNSUPPORTED_STORAGE;
             } catch (IllegalAccessException e) {
                 throw new AssertionError(e); // Should never happen, since the 
method is public.
             } catch (InvocationTargetException e) {
@@ -152,7 +152,7 @@ public class NetcdfStoreProvider extends
                 return ProbeResult.SUPPORTED;
             }
         }
-        return ProbeResult.UNKNOWN_FORMAT;
+        return ProbeResult.UNSUPPORTED_STORAGE;
     }
 
     /**

Modified: 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelDataInput.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelDataInput.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelDataInput.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelDataInput.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -59,7 +59,7 @@ import java.nio.channels.SeekableByteCha
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.07)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public class ChannelDataInput {
@@ -150,6 +150,34 @@ public class ChannelDataInput {
     }
 
     /**
+     * Tries to read more bytes from the channel without changing the buffer 
position.
+     * This method returns a negative number if the buffer is already full or 
if the channel reached the
+     * <cite>end of stream</cite>. Otherwise this method reads an arbitrary 
amount of bytes not greater
+     * than the space available in the buffer, and returns the amount bytes 
actually read.
+     *
+     * @return The number of bytes read, or -2 if the buffer is full, or -1 on 
<cite>end of stream</cite>.
+     * @throws IOException If an error occurred while reading the bytes.
+     *
+     * @since 0.4
+     */
+    public final int prefetch() throws IOException {
+        final int limit    = buffer.limit();
+        final int capacity = buffer.capacity();
+        if (limit == capacity) {
+            return -2;
+        }
+        final int position = buffer.position();
+        buffer.limit(capacity).position(limit);
+        int c = channel.read(buffer);
+        while (c == 0) {
+            onEmptyChannelBuffer();
+            c = channel.read(buffer);
+        }
+        buffer.limit(buffer.position()).position(position);
+        return c;
+    }
+
+    /**
      * Returns {@code true} if the buffer or the channel has at least one byte 
remaining.
      * If the {@linkplain #buffer} has no remaining bytes, then this method 
will attempts
      * to read at least one byte from the {@linkplain #channel}. If no bytes 
can be read
@@ -708,8 +736,9 @@ public class ChannelDataInput {
                     }
                     onEmptyChannelBuffer();
                 }
+                buffer.flip();
             } while (p > buffer.limit());
-            buffer.flip().position((int) p);
+            buffer.position((int) p);
         } else {
             /*
              * Requested position is before the current buffer limits

Modified: 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreProvider.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -67,7 +67,7 @@ public abstract class DataStoreProvider 
      * <ul>
      *   <li>{@link ProbeResult#SUPPORTED} if the {@code DataStore}s created 
by this provider
      *       can open the given storage.</li>
-     *   <li>{@link ProbeResult#UNKNOWN_STORAGE} if the given storage does not 
appear to be in a format
+     *   <li>{@link ProbeResult#UNSUPPORTED_STORAGE} if the given storage does 
not appear to be in a format
      *       supported by this {@code DataStoreProvider}.</li>
      * </ul>
      *
@@ -90,18 +90,18 @@ public abstract class DataStoreProvider 
      *         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.UNKNOWN_STORAGE;
+     *             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.UNDETERMINED;
+     *             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.UNKNOWN_FORMAT;
+     *             return ProbeResult.UNSUPPORTED_STORAGE;
      *         }
      *         return ProbeResult.SUPPORTED;
      *     }

Modified: 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -16,6 +16,9 @@
  */
 package org.apache.sis.storage;
 
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Iterator;
 import java.util.ServiceLoader;
 import org.apache.sis.util.ThreadSafe;
 import org.apache.sis.util.ArgumentChecks;
@@ -89,22 +92,77 @@ final class DataStoreRegistry {
         } else {
             connector = new StorageConnector(storage);
         }
+        DataStoreProvider provider = null;
+        List<DataStoreProvider> deferred = null;
         try {
-            DataStoreProvider provider = null;
+            /*
+             * TODO: we may have thread contention here.
+             * We are waiting to see if this is a problem in practice.
+             */
             synchronized (loader) {
 search:         for (final DataStoreProvider candidate : loader) {
                     switch (candidate.canOpen(connector)) {
+                        /*
+                         * Stop at the first provider claiming to be able to 
read the storage.
+                         * We will exit the synchronized block before to open 
the storage.
+                         */
                         case SUPPORTED: {
                             provider = candidate;
+                            deferred = null;
                             break search;
                         }
+                        /*
+                         * If a provider doesn't have enough bytes for 
answering the question,
+                         * try again after this loop with more bytes in the 
buffer, unless we
+                         * found an other provider.
+                         */
+                        case INSUFFICIENT_BYTES: {
+                            if (deferred == null) {
+                                deferred = new LinkedList<>();
+                            }
+                            deferred.add(candidate);
+                            break;
+                        }
+                        /*
+                         * If a provider doesn't know whether it can open the 
given storage,
+                         * we will try it only if we find no provider retuning 
SUPPORTED.
+                         *
+                         * TODO: What to do if we find more than one provider 
here? We can not invoke
+                         *       provider.open(connector) in a try … catch 
block because it may leave
+                         *       the StorageConnector in an invalid state in 
case of failure.
+                         */
                         case UNDETERMINED: {
-                            // TODO: not enough information.
+                            provider = candidate;
                             break;
                         }
                     }
                 }
             }
+            /*
+             * If any provider did not had enough bytes for answering the 
'canOpen(…)' question,
+             * get more bytes and try again. We try to prefetch more bytes 
only if we have no choice
+             * in order to avoid latency on network connection.
+             */
+            if (deferred != null) {
+search:         while (!deferred.isEmpty() && connector.prefetch()) {
+                    for (final Iterator<DataStoreProvider> 
it=deferred.iterator(); it.hasNext();) {
+                        final DataStoreProvider candidate = it.next();
+                        switch (candidate.canOpen(connector)) {
+                            case SUPPORTED:          provider = candidate; 
break search;
+                            case UNDETERMINED:       provider = candidate; 
break;
+                            case INSUFFICIENT_BYTES: continue; // Will try 
again in next iteration.
+                        }
+                        it.remove(); // UNSUPPORTED_* or UNDETERMINED: do not 
try again those providers.
+                    }
+                }
+            }
+            /*
+             * If a provider has been found, or if a provider returned 
UNDETERMINED, use that one
+             * for opening a DataStore. Note that if more than one provider 
returned UNDETERMINED,
+             * the selected one is arbitrary and may change in different 
execution. Implementors
+             * shall avoid the UNDETERMINED value as much as possible (this 
value should be used
+             * only for RAW image format).
+             */
             if (provider != null) {
                 final DataStore data = provider.open(connector);
                 connector = null; // For preventing it to be closed.

Modified: 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/ProbeResult.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/ProbeResult.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/ProbeResult.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/ProbeResult.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -23,12 +23,11 @@ package org.apache.sis.storage;
  *
  * <ul>
  *   <li>{@link #SUPPORTED} indicates that the storage can be read and 
eventually written.</li>
- *   <li>{@link #UNDETERMINED} indicates that the provider does not have 
enough information for telling
- *       whether the storage can be opened. SIS will try to use such provider 
last, if no better suited
- *       provider is found.</li>
- *   <li>All other values indicate that the storage can not be opened. The 
actual enumeration value gives
- *       the reason (e.g. {@linkplain #UNKNOWN_FORMAT unknown format}, or
- *       {@linkplain #UNSUPPORTED_VERSION unsupported version}).</li>
+ *   <li>{@code UNSUPPORTED_*} indicate that the storage can not be opened. 
The actual enumeration value gives
+ *       the reason (e.g. unsupported format or {@linkplain 
#UNSUPPORTED_VERSION unsupported version}).</li>
+ *   <li>{@link #INSUFFICIENT_BYTES} or {@link #UNDETERMINED} indicate that 
the provider does not have enough
+ *       information for telling whether the storage can be opened. SIS will 
try to use such provider last,
+ *       if no better suited provider is found.</li>
  * </ul>
  *
  * When a {@link DataStores#open DataStores.open(…)} method is invoked, SIS 
will iterate over the list of known
@@ -52,34 +51,56 @@ public enum ProbeResult {
     SUPPORTED,
 
     /**
-     * The open capability can not be determined.
-     * This value may be returned in two kinds of situation:
+     * The {@code DataStoreProvider} does not recognize the given storage 
object, file format or database schema.
+     * Examples:
      *
      * <ul>
-     *   <li>The method can not look ahead far enough in the file header,
-     *       for example because the buffer has not fetched enough bytes.</li>
-     *   <li>The {@code DataStore} could potentially open anything.
-     *       This is the case for example of the RAW image format.</li>
+     *   <li>The storage is a file while the provider expected a database 
connection (or conversely).</li>
+     *   <li>The file does not contains the expected magic number.</li>
+     *   <li>The database schema does not contain the expected tables.</li>
      * </ul>
      */
-    UNDETERMINED,
+    UNSUPPORTED_STORAGE,
 
     /**
-     * The {@code DataStoreProvider} does not recognize the given storage 
object.
-     * For example the storage may be a file while the provider expected a 
database connection, or conversely.
+     * The {@code DataStoreProvider} recognizes the given storage, but the 
data are structured
+     * according a file or schema version not yet supported by the current 
implementation.
      */
-    UNKNOWN_STORAGE,
+    UNSUPPORTED_VERSION,
 
     /**
-     * The {@code DataStoreProvider} does not recognize the file format or 
schema.
-     * For example the file does not contains the expected magic number,
-     * or the database schema does not contain the expected tables.
+     * The open capability can not be determined because the {@link 
ByteBuffer} contains an insufficient
+     * amount of bytes. This value may be returned by {@link 
DataStoreProvider#canOpen(StorageConnector)}
+     * implementations similar to the following:
+     *
+     * {@preformat java
+     *     public ProbeResult canOpen(StorageConnector storage) throws 
DataStoreException {
+     *         final ByteBuffer buffer = 
storage.getStorageAs(ByteBuffer.class);
+     *         if (buffer == null) {
+     *             return ProbeResult.UNSUPPORTED_STORAGE;
+     *         }
+     *         if (buffer.remaining() < Integer.SIZE / Byte.SIZE) {
+     *             return ProbeResult.INSUFFICIENT_BYTES;
+     *         }
+     *         // Other verifications here.
+     *     }
+     * }
+     *
+     * When some {@code DataStoreProvider} return this value, SIS will first 
continue the search for a provider that
+     * can answer the {@code canOpen(…)} question using only the available 
bytes. Only if no provider is found,
+     * then SIS will fetch more bytes and try again the providers that 
returned {@code INSUFFICIENT_BYTES}.
+     * SIS tries to work with available bytes before to ask more in order to 
reduce latencies on network connections.
      */
-    UNKNOWN_FORMAT,
+    INSUFFICIENT_BYTES,
 
     /**
-     * The {@code DataStoreProvider} recognizes the given storage, but the 
data are structured
-     * according a file or schema version not yet supported by the current 
implementation.
+     * The open capability can not be determined.
+     * This value may be returned by {@code DataStore} implementations that 
could potentially open anything,
+     * as for example of the RAW image format.
+     *
+     * <p><strong>This is a last resort value!</strong> {@code canOpen(…)} 
implementations are strongly encouraged
+     * to return a more accurate enumeration value for allowing {@link 
DataStores#open(Object)} to perform a better
+     * choice. Generally, this value should be used only by the RAW image 
format.</p>
      */
-    UNSUPPORTED_VERSION
+    UNDETERMINED
 }

Modified: 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -31,7 +31,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;
@@ -82,6 +81,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.
+     */
+    private static final int MINIMAL_BUFFER_SIZE = 256;
+
+    /**
      * The input/output object given at construction time.
      *
      * @see #getStorage()
@@ -364,6 +369,7 @@ 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.
@@ -426,14 +432,18 @@ public class StorageConnector implements
     }
 
     /**
-     * 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);
         }
@@ -442,15 +452,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.
                 }
             }
         }
@@ -458,6 +475,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#canOpen(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.
      *

Modified: 
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java?rev=1510624&r1=1510623&r2=1510624&view=diff
==============================================================================
--- 
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java
 [UTF-8] (original)
+++ 
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ChannelDataInputTest.java
 [UTF-8] Mon Aug  5 17:47:23 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());
@@ -173,7 +187,7 @@ public final strictfp class ChannelDataI
         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) {
@@ -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;
     }
 }

Added: 
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/DripByteChannel.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/DripByteChannel.java?rev=1510624&view=auto
==============================================================================
--- 
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/DripByteChannel.java
 (added)
+++ 
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/DripByteChannel.java
 [UTF-8] Mon Aug  5 17:47:23 2013
@@ -0,0 +1,114 @@
+/*
+ * 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.internal.storage;
+
+import java.util.Random;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ClosedChannelException;
+
+import static java.lang.StrictMath.min;
+
+
+/**
+ * A {@link ReadableByteChannel} with a {@code read} methods that do not 
return all available bytes.
+ * This class is used for simulating a socket connection where some data may 
not be immediately available
+ * from the socket's input buffer.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.4
+ * @version 0.4
+ * @module
+ */
+public final strictfp class DripByteChannel implements ReadableByteChannel {
+    /**
+     * The data to provide.
+     */
+    private final byte[] data;
+
+    /**
+     * The random number generator to use for determining how many bytes to 
return.
+     */
+    private final Random random;
+
+    /**
+     * Minimal (inclusive) and maximal (exclusive) amount of bytes to read.
+     */
+    private final int lower, upper;
+
+    /**
+     * Current position in the data array.
+     */
+    private int position;
+
+    /**
+     * Sets to {@code true} after {@link #close()} has been invoked.
+     */
+    private boolean isClosed;
+
+    /**
+     * Creates a new {@code DripByteChannel} wrapping the given data.
+     *
+     * @param data    The data to provide.
+     * @param random  The random number generator to use for determining how 
many bytes to return.
+     * @param lower   Minimal amount of bytes to read, inclusive.
+     * @param upper   Maximal amount of bytes to read, exclusive.
+     */
+    public DripByteChannel(final byte[] data, final Random random, final int 
lower, final int upper) {
+        this.data    = data;
+        this.random  = random;
+        this.lower   = lower;
+        this.upper   = upper;
+    }
+
+    /**
+     * Reads a random number of bytes from the data array.
+     *
+     * @param buffer The buffer where to copy the bytes.
+     */
+    @Override
+    public int read(final ByteBuffer buffer) throws IOException {
+        if (isClosed) {
+            throw new ClosedChannelException();
+        }
+        final int remaining = data.length - position;
+        if (remaining == 0) {
+            return -1;
+        }
+        final int n = min(random.nextInt(upper - lower) + lower, 
min(remaining, buffer.remaining()));
+        buffer.put(data, position, n);
+        position += n;
+        return n;
+    }
+
+    /**
+     * Returns {@code true} if the channel has not yet been closed.
+     */
+    @Override
+    public boolean isOpen() {
+        return !isClosed;
+    }
+
+    /**
+     * Closes the channel.
+     */
+    @Override
+    public void close() {
+        isClosed = true;
+    }
+}

Propchange: 
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/DripByteChannel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/DripByteChannel.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8


Reply via email to