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