sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1777424 - in /sis/branches/JDK8: core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/ storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ storage...
Date Thu, 05 Jan 2017 07:16:38 GMT
Author: desruisseaux
Date: Thu Jan  5 07:16:37 2017
New Revision: 1777424

URL: http://svn.apache.org/viewvc?rev=1777424&view=rev
Log:
First draft of a mechanism for re-opening a new input stream if a DataStore needs to read the same data more than once.

Added:
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelFactory.java
      - copied, changed from r1777268, sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java
Modified:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
    sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxDataStore.java

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -48,7 +48,8 @@ import org.apache.sis.util.Classes;
  *         <ul>
  *           <li>The {@linkplain MetadataStandard#getInterface(Class) standard type} of the source element
  *               is assignable to the type of the target element.</li>
- *           <li>There is no conflict, i.e. no property value that are not collection and not equal.</li>
+ *           <li>There is no conflict, i.e. no property value that are not collection and not equal
+ *               (note: this condition is disabled if {@link #avoidConflicts} is {@code false}).</li>
  *         </ul>
  *         If such pair is found, then the merge operation if performed recursively
  *         for that pair of source and target elements.</li>
@@ -78,6 +79,14 @@ public class Merger {
     protected final Locale locale;
 
     /**
+     * {@code true} for performing a greater effort of avoiding merge conflicts.
+     * The default value is {@code false}, which may cause {@link #unmerged unmerged(…)} to be invoked in
+     * situation where it could have been avoided. Setting this value to {@code true} increase the chances
+     * of merge success at the expense of more computations.
+     */
+    public boolean avoidConflicts;
+
+    /**
      * Creates a new merger.
      *
      * @param  locale  the locale to use for formatting error messages, or {@code null} for the default locale.
@@ -216,11 +225,16 @@ public class Merger {
                             final Iterator<?> it = sourceList.iterator();
                             while (it.hasNext()) {
                                 final Object value = it.next();
-                                if (merge(value, (ModifiableMetadata) element, true) &&
-                                    merge(value, (ModifiableMetadata) element, false))
-                                {
-                                    it.remove();
-                                    break;          // Merge at most one source element to each target element.
+                                if (!avoidConflicts || merge(value, (ModifiableMetadata) element, true)) {
+                                    /*
+                                     * If enabled, above 'merge' call verified that the merge can be done, including
+                                     * by recursive checks in all children. The intend is to have a "all or nothing"
+                                     * behavior, before the 'merge' call below starts to modify the values.
+                                     */
+                                    if (merge(value, (ModifiableMetadata) element, false)) {
+                                        it.remove();
+                                        break;          // Merge at most one source element to each target element.
+                                    }
                                 }
                             }
                         }
@@ -267,6 +281,9 @@ public class Merger {
      * The default implementation throws an {@link InvalidMetadataException}.
      * Subclasses can override this method if they want to perform a different processing.
      *
+     * <p><b>Tip:</b> to reduce the risks that this method is invoked, consider setting the
+     * {@link #avoidConflicts} flag to {@code true} before invoking {@link #merge merge(…)}.</p>
+     *
      * @param target        the metadata instance in which the value should have been written.
      * @param propertyName  the name of the property to write.
      * @param sourceValue   the value to write.

Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -100,6 +100,7 @@ public final strictfp class MergerTest e
         final DefaultMetadata source = createSample1();
         final DefaultMetadata target = createSample2();
         final Merger merger = new Merger(null);
+        merger.avoidConflicts = true;
         merger.merge(source, target);
 
         assertSetEquals(Arrays.asList(Locale.JAPANESE, Locale.FRENCH),  target.getLanguages());

Copied: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelFactory.java (from r1777268, sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelFactory.java?p2=sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelFactory.java&p1=sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java&r1=1777268&r2=1777424&rev=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ChannelFactory.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -21,16 +21,12 @@ import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Collections;
 import java.util.Arrays;
-import java.util.Locale;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.IOException;
-import java.io.LineNumberReader;
 import java.net.URI;
 import java.net.URL;
-import java.net.URLDecoder;
-import java.net.URISyntaxException;
 import java.net.MalformedURLException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -41,377 +37,52 @@ import java.nio.file.OpenOption;
 import java.nio.file.StandardOpenOption;
 import java.nio.channels.Channels;
 import java.nio.channels.ReadableByteChannel;
-import java.nio.charset.StandardCharsets;
-import javax.xml.stream.Location;
-import javax.xml.stream.XMLStreamReader;
 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;
 import org.apache.sis.internal.system.Modules;
 
 
 /**
- * Utility methods related to I/O operations. Many methods in this class accept arbitrary {@link Object} argument
- * and perform a sequence of {@code instanceof} checks. Since this approach provides no type safety and since the
- * sequence of {@code instanceof} checks is somewhat arbitrary, those methods can not be in public API.
+ * Opens a readable channel for a given input object (URL, input stream, <i>etc</i>).
+ * The {@link #prepare prepare(…)} method analyzes the given input {@link Object} and tries to return a factory instance
+ * capable to open at least one {@link ReadableByteChannel} for that input. For some kinds of input like {@link Path} or
+ * {@link URL}, the {@link #reader()} method can be invoked an arbitrary amount of times for creating as many channels
+ * as needed. But for other kinds of input like {@link InputStream}, only one channel can be returned. In such case,
+ * only the first {@link #reader()} method invocation will succeed and all subsequent ones will throw an exception.
  *
- * <p>Unless otherwise specified, giving an instance of unknown type or a {@code null} value cause the methods to
- * return {@code null}. No exception is thrown for unknown type - callers must check that the return value is not
- * null. However exceptions may be thrown for malformed URI or URL.</p>
+ * <div class="section">Multi-threading</div>
+ * This class is not thread-safe, except for the static {@link #prepare prepare(…)} method.
+ * Callers are responsible for synchronizing their call to any member methods ({@link #reader()}, <i>etc</i>).
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @since   0.3
+ * @since   0.8
  * @version 0.8
  * @module
  */
-public final class IOUtilities extends Static {
+public abstract class ChannelFactory {
     /**
-     * Options to be rejected by {@link #open(Object, String, OpenOption[])} for safety reasons.
+     * Options to be rejected by {@link #create(Object, String, OpenOption[])} for safety reasons.
      */
     private static final Set<StandardOpenOption> ILLEGAL_OPTIONS = EnumSet.of(
             StandardOpenOption.APPEND, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DELETE_ON_CLOSE);
 
     /**
-     * Do not allow instantiation of this class.
+     * For subclass constructors.
      */
-    private IOUtilities() {
+    ChannelFactory() {
     }
 
     /**
-     * Returns the filename from a {@link Path}, {@link File}, {@link URL}, {@link URI} or {@link CharSequence}
-     * instance. If the given argument is specialized type like {@code Path} or {@code File}, then this method uses
-     * dedicated API like {@link Path#getFileName()}. Otherwise this method gets a string representation of the path
-     * and returns the part after the last {@code '/'} or platform-dependent name separator character, if any.
-     *
-     * @param  path  the path as an instance of one of the above-cited types, or {@code null}.
-     * @return the filename in the given path, or {@code null} if the given object is null or of unknown type.
-     */
-    public static String filename(final Object path) {
-        return part(path, false);
-    }
-
-    /**
-     * Returns the filename extension from a {@link Path}, {@link File}, {@link URL}, {@link URI} or
-     * {@link CharSequence} instance. If no extension is found, returns an empty string. If the given
-     * object is of unknown type, return {@code null}.
-     *
-     * @param  path  the path as an instance of one of the above-cited types, or {@code null}.
-     * @return the extension in the given path, or an empty string if none, or {@code null}
-     *         if the given object is null or of unknown type.
-     */
-    public static String extension(final Object path) {
-        return part(path, true);
-    }
-
-    /**
-     * Implementation of {@link #filename(Object)} and {@link #extension(Object)} methods.
-     */
-    private static String part(final Object path, final boolean extension) {
-        int fromIndex = 0;
-        final String name;
-        if (path instanceof File) {
-            name = ((File) path).getName();
-        } else if (path instanceof Path) {
-            name = ((Path) path).getFileName().toString();
-        } else {
-            char separator = '/';
-            if (path instanceof URL) {
-                name = ((URL) path).getPath();
-            } else if (path instanceof URI) {
-                final URI uri = (URI) path;
-                name = uri.isOpaque() ? uri.getSchemeSpecificPart() : uri.getPath();
-            } else if (path instanceof CharSequence) {
-                name = path.toString();
-                separator = File.separatorChar;
-            } else {
-                return null;
-            }
-            fromIndex = name.lastIndexOf('/') + 1;
-            if (separator != '/') {
-                // Search for platform-specific character only if the object is neither a URL or a URI.
-                fromIndex = Math.max(fromIndex, CharSequences.lastIndexOf(name, separator, fromIndex, name.length()) + 1);
-            }
-        }
-        if (extension) {
-            fromIndex = CharSequences.lastIndexOf(name, '.', fromIndex, name.length()) + 1;
-            if (fromIndex <= 1) { // If the dot is the first character, do not consider as a filename extension.
-                return "";
-            }
-        }
-        return name.substring(fromIndex);
-    }
-
-    /**
-     * Returns a string representation of the given path, or {@code null} if none. The current implementation
-     * recognizes only the {@link Path}, {@link File}, {@link URL}, {@link URI} or {@link CharSequence} types.
-     *
-     * @param  path  the path for which to return a string representation.
-     * @return the string representation, or {@code null} if none.
-     */
-    public static String toString(final Object path) {
-        // For the following types, the string that we want can be obtained only by toString(),
-        // or the class is final so we know that the toString(à behavior can not be changed.
-        if (path instanceof CharSequence || path instanceof Path || path instanceof URL || path instanceof URI) {
-            return path.toString();
-        }
-        // While toString() would work too on the default implementation, the following
-        // type is not final. So we are better to invoke the dedicated method.
-        if (path instanceof File) {
-            return ((File) path).getPath();
-        }
-        return null;
-    }
-
-    /**
-     * Encodes the characters that are not legal for the {@link URI#URI(String)} constructor.
-     * Note that in addition to unreserved characters ("{@code _-!.~'()*}"), the reserved
-     * characters ("{@code ?/[]@}") and the punctuation characters ("{@code ,;:$&+=}")
-     * are left unchanged, so they will be processed with their special meaning by the
-     * URI constructor.
-     *
-     * <p>The current implementations replaces only the space characters, control characters
-     * and the {@code %} character. Future versions may replace more characters as we learn
-     * from experience.</p>
-     *
-     * @param  path  the path to encode, or {@code null}.
-     * @return the encoded path, or {@code null} if and only if the given path was null.
-     */
-    public static String encodeURI(final String path) {
-        if (path == null) {
-            return null;
-        }
-        StringBuilder buffer = null;
-        final int length = path.length();
-        for (int i=0; i<length;) {
-            final int c = path.codePointAt(i);
-            final int n = Character.charCount(c);
-            if (!Character.isSpaceChar(c) && !Character.isISOControl(c) && c != '%') {
-                /*
-                 * The character is valid, or is punction character, or is a reserved character.
-                 * All those characters should be handled properly by the URI(String) constructor.
-                 */
-                if (buffer != null) {
-                    buffer.appendCodePoint(c);
-                }
-            } else {
-                /*
-                 * The character is invalid, so we need to escape it. Note that the encoding
-                 * is fixed to UTF-8 as of java.net.URI specification (see its class javadoc).
-                 */
-                if (buffer == null) {
-                    buffer = new StringBuilder(path);
-                    buffer.setLength(i);
-                }
-                for (final byte b : path.substring(i, i+n).getBytes(StandardCharsets.UTF_8)) {
-                    buffer.append('%');
-                    final String hex = Integer.toHexString(Byte.toUnsignedInt(b)).toUpperCase(Locale.ROOT);
-                    if (hex.length() < 2) {
-                        buffer.append('0');
-                    }
-                    buffer.append(hex);
-                }
-            }
-            i += n;
-        }
-        return (buffer != null) ? buffer.toString() : path;
-    }
-
-    /**
-     * Converts a {@link URL} to a {@link URI}. This is equivalent to a call to the standard {@link URL#toURI()}
-     * method, except for the following functionalities:
-     *
-     * <ul>
-     *   <li>Optionally decodes the {@code "%XX"} sequences, where {@code "XX"} is a number.</li>
-     *   <li>Converts various exceptions into subclasses of {@link IOException}.</li>
-     * </ul>
-     *
-     * @param  url       the URL 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 not encoded,
-     *                   then {@code null}.
-     * @return the URI for the given URL, or {@code null} if the given URL was null.
-     * @throws IOException if the URL can not be converted to a URI.
-     *
-     * @see URI#URI(String)
-     */
-    public static URI toURI(final URL url, final String encoding) throws IOException {
-        if (url == null) {
-            return null;
-        }
-        /*
-         * Convert the URL to an URI, taking in account the encoding if any.
-         *
-         * Note: URL.toURI() is implemented as new URI(URL.toString()) where toString()
-         * delegates to toExternalForm(), and all those methods are final. So we really
-         * don't lost anything by doing those steps ourself.
-         */
-        String path = url.toExternalForm();
-        if (encoding != null) {
-            path = URLDecoder.decode(path, encoding);
-        }
-        path = encodeURI(path);
-        try {
-            return new URI(path);
-        } catch (URISyntaxException cause) {
-            /*
-             * Occurs only if the URL is not compliant with RFC 2396. Otherwise every URL
-             * should succeed, so a failure can actually be considered as a malformed URL.
-             */
-            throw (MalformedURLException) new MalformedURLException(Exceptions.formatChainedMessages(null,
-                    Errors.format(Errors.Keys.IllegalArgumentValue_2, "URL", path), cause)).initCause(cause);
-        }
-    }
-
-    /**
-     * Converts a {@link URL} to a {@link File}. This is equivalent to a call to the standard
-     * {@link URL#toURI()} method followed by a call to the {@link File#File(URI)} constructor,
-     * except for the following functionalities:
-     *
-     * <ul>
-     *   <li>Optionally decodes the {@code "%XX"} sequences, where {@code "XX"} is a number.</li>
-     *   <li>Converts various exceptions into subclasses of {@link IOException}.</li>
-     * </ul>
-     *
-     * @param  url       the URL 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 not encoded,
-     *                   then {@code null}.
-     * @return the file for the given URL, or {@code null} if the given URL was null.
-     * @throws IOException if the URL can not be converted to a file.
-     *
-     * @see File#File(URI)
-     */
-    public static File toFile(final URL url, final String encoding) throws IOException {
-        if (url == null) {
-            return null;
-        }
-        final URI uri = toURI(url, encoding);
-        /*
-         * We really want to call the File constructor expecting a URI argument,
-         * not the constructor expecting a String argument, because the one for
-         * the URI argument performs additional platform-specific parsing.
-         */
-        try {
-            return new File(uri);
-        } catch (IllegalArgumentException cause) {
-            /*
-             * 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.
-             */
-            throw new IOException(Exceptions.formatChainedMessages(null,
-                    Errors.format(Errors.Keys.IllegalArgumentValue_2, "URL", url), cause), cause);
-        }
-    }
-
-    /**
-     * Converts a {@link URL} to a {@link Path}. This is equivalent to a call to the standard
-     * {@link URL#toURI()} method followed by a call to the {@link Paths#get(URI)} static method,
-     * except for the following functionalities:
-     *
-     * <ul>
-     *   <li>Optionally decodes the {@code "%XX"} sequences, where {@code "XX"} is a number.</li>
-     *   <li>Converts various exceptions into subclasses of {@link IOException}.</li>
-     * </ul>
-     *
-     * @param  url       the URL 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 not encoded,
-     *                   then {@code null}.
-     * @return the path for the given URL, or {@code null} if the given URL was null.
-     * @throws IOException if the URL can not be converted to a path.
-     *
-     * @see Paths#get(URI)
-     */
-    public static Path toPath(final URL url, final String encoding) throws IOException {
-        if (url == null) {
-            return null;
-        }
-        final URI uri = toURI(url, encoding);
-        try {
-            return Paths.get(uri);
-        } catch (IllegalArgumentException | FileSystemNotFoundException cause) {
-            final String message = Exceptions.formatChainedMessages(null,
-                    Errors.format(Errors.Keys.IllegalArgumentValue_2, "URL", url), cause);
-            /*
-             * If the exception is IllegalArgumentException, then the URI scheme has been recognized
-             * but the URI syntax is illegal for that file system. So we can consider that the URL is
-             * malformed in regard to the rules of that particular file system.
-             */
-            final IOException e;
-            if (cause instanceof IllegalArgumentException) {
-                e = new MalformedURLException(message);
-                e.initCause(cause);
-            } else {
-                e = new IOException(message, cause);
-            }
-            throw e;
-        }
-    }
-
-    /**
-     * Parses the following path as a {@link File} if possible, or a {@link URL} otherwise.
-     * In the special case where the given {@code path} is a URL using the {@code "file"} protocol,
-     * the URL is converted to a {@link File} object using the given {@code encoding} for decoding
-     * the {@code "%XX"} sequences, if any.
-     *
-     * <div class="section">Rational</div>
-     * 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.
-     *
-     * @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 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 path as a {@link File} if possible, or a {@link URL} otherwise.
-     * @throws IOException if the given path is not a file and can't be parsed as a URL.
-     */
-    public static Object toFileOrURL(final String path, final String encoding) throws IOException {
-        if (path == null) {
-            return null;
-        }
-        /*
-         * 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(':');
-            /*
-             * If the ':' character is found, the part before it is probably a protocol in a URL,
-             * except in the particular case where there is just one letter before ':'. In such
-             * case, it may be the drive letter of a Windows file.
-             */
-            if (s<0 || (s==1 && Character.isLetter(path.charAt(0)) && !path.regionMatches(2, "//", 0, 2))) {
-                return new File(path);
-            }
-        }
-        final URL url = new URL(path);
-        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;
-    }
-
-    /**
-     * Returns a byte channel from the given input, or {@code null} if the input is of unknown type.
+     * Returns a byte channel factory from the given input, or {@code null} if the input is of unknown type.
      * More specifically:
      *
      * <ul>
      *   <li>If the given input is {@code null}, then this method returns {@code null}.</li>
      *   <li>If the given input is a {@link ReadableByteChannel} or an {@link InputStream},
-     *       then it is returned directly, or indirectly as a wrapper.</li>
+     *       then the factory will return that input directly or indirectly as a wrapper.</li>
      *   <li>If the given input if a {@link Path}, {@link File}, {@link URL}, {@link URI}
-     *       or {@link CharSequence}, then a new channel is opened.</li>
+     *       or {@link CharSequence}, then the factory will open new channels on demand.</li>
      * </ul>
      *
      * The given options are used for opening the channel on a <em>best effort basis</em>.
@@ -432,10 +103,10 @@ public final class IOUtilities extends S
      *                   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.
+     * @return the channel factory for the given input, or {@code null} if the given input is of unknown type.
+     * @throws IOException if an error occurred while processing the given input.
      */
-    public static ReadableByteChannel open(Object input, final String encoding, OpenOption... options) throws IOException {
+    public static ChannelFactory prepare(Object input, final String encoding, OpenOption... options) throws IOException {
         /*
          * Unconditionally verify the options, even if we may not use them.
          */
@@ -451,19 +122,15 @@ public final class IOUtilities extends S
             }
         }
         /*
-         * 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.
+         * Check for inputs that are already readable channels or input streams.
+         * Note that Channels.newChannel(InputStream) checks for instances of FileInputStream in order to delegate
+         * to its getChannel() method, but only if the input stream type is exactly FileInputStream, not a subtype.
+         * If Apache SIS defines its own FileInputStream subclass someday, we may need to add a special case here.
          */
         if (input instanceof ReadableByteChannel) {
-            return (ReadableByteChannel) input;
-        }
-        if (input instanceof InputStream) {
-            if (input instanceof FileInputStream) {
-                return ((FileInputStream) input).getChannel();
-            }
-            return Channels.newChannel((InputStream) input);
+            return new Stream((ReadableByteChannel) input);
+        } else if (input instanceof InputStream) {
+            return new Stream(Channels.newChannel((InputStream) input));
         }
         /*
          * In the following cases, we will try hard to convert to Path objects before to fallback
@@ -471,11 +138,12 @@ public final class IOUtilities extends S
          */
         if (input instanceof URL) {
             try {
-                input = toPath((URL) input, encoding);
+                input = IOUtilities.toPath((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.
+                /*
+                 * 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 the Path.
+                 */
                 recoverableException(e);
             }
         } else if (input instanceof URI) {
@@ -486,8 +154,10 @@ public final class IOUtilities extends S
              */
             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.
+                /*
+                 * 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(Resources.format(Resources.Keys.MissingSchemeInURI_1, uri));
             } else try {
                 input = Paths.get(uri);
@@ -498,14 +168,16 @@ public final class IOUtilities extends S
                     ioe.addSuppressed(e);
                     throw ioe;
                 }
-                // 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.
+                /*
+                 * 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);
+            if (input instanceof CharSequence) {    // Needs to be before the check for File or URL.
+                input = IOUtilities.toFileOrURL(input.toString(), encoding);
             }
             /*
              * If the input is a File or a CharSequence that we have been able to convert to a File,
@@ -513,23 +185,15 @@ public final class IOUtilities extends S
              * to convert to a Path (which is unlikely), we will use directly the File.
              */
             if (input instanceof File) {
+                final File file = (File) input;
                 try {
-                    input = ((File) input).toPath();
-                } catch (InvalidPathException e) {
-                    // Unlikely to happen. But if it happens anyway, try to open the channel in a
-                    // way less surprising for the user (closer to the object he has specified).
-                    final ReadableByteChannel channel;
-                    try {
-                        channel = new FileInputStream((File) input).getChannel();
-                    } catch (IOException ioe) {
-                        ioe.addSuppressed(e);
-                        throw ioe;
-                    }
-                    // We have been able to create a channel, maybe not with the given OpenOptions.
-                    // But the exception was nevertheless unexpected, so log its stack trace in order
-                    // to allow the developer to check if there is something wrong.
-                    Logging.unexpectedException(Logging.getLogger(Modules.STORAGE), IOUtilities.class, "open", e);
-                    return channel;
+                    input = file.toPath();
+                } catch (final InvalidPathException e) {
+                    /*
+                     * Unlikely to happen. But if it happens anyway, try to open the channel in a
+                     * way less surprising for the user (closer to the object he has specified).
+                     */
+                    return new Fallback(file, e);
                 }
             }
         }
@@ -539,103 +203,166 @@ public final class IOUtilities extends S
          * 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());
+            final URL file = (URL) input;
+            return new ChannelFactory() {
+                @Override public ReadableByteChannel reader() throws IOException {
+                    return Channels.newChannel(file.openStream());
+                }
+            };
         }
         if (input instanceof Path) {
-            return Files.newByteChannel((Path) input, optionSet);
+            final Path path = (Path) input;
+            return new ChannelFactory() {
+                @Override public ReadableByteChannel reader() throws IOException {
+                    return Files.newByteChannel(path, 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.
+     * Returns {@code true} if this factory is capable to create another reader. This method returns {@code false}
+     * if this factory is capable to create only one channel and {@link #reader()} has already been invoked.
+     *
+     * @return whether {@link #reader()} can be invoked.
      */
-    private static void recoverableException(final Exception warning) {
-        Logging.recoverableException(Logging.getLogger(Modules.STORAGE), IOUtilities.class, "open", warning);
+    public boolean canOpen() {
+        return true;
     }
 
     /**
-     * Returns {@code true} if the given options would open a file mostly for writing.
-     * This method returns {@code true} if the following conditions are true:
-     *
-     * <ul>
-     *   <li>The array contains {@link StandardOpenOption#WRITE}.</li>
-     *   <li>The array does not contain {@link StandardOpenOption#READ}, unless the array contains also
-     *       {@link StandardOpenOption#CREATE_NEW} or {@link StandardOpenOption#TRUNCATE_EXISTING} in which
-     *       case the {@code READ} option is ignored (because the caller would have no data to read).</li>
-     * </ul>
+     * Returns the channel as an input stream. The returned stream is <strong>not</strong> buffered;
+     * it is caller's responsibility to wrap the stream in a {@link java.io.BufferedInputStream} if desired.
      *
-     * @param  options  the open options to check, or {@code null} if none.
-     * @return {@code true} if a file opened with the given options would be mostly for write operations.
+     * @return the input stream.
+     * @throws IOException if the input stream or its underlying byte channel can not be created.
+     */
+    public InputStream inputStream() throws IOException {
+        return Channels.newInputStream(reader());
+    }
+
+    /**
+     * Returns a byte channel from the input given to the {@link #prepare prepare(…)} method.
+     * If the channel has already been created and this method can not create it twice, then
+     * this method throw an exception.
      *
-     * @since 0.8
+     * @return the channel for the given input.
+     * @throws IOException if an error occurred while opening the channel.
      */
-    public static boolean isWrite(final OpenOption[] options) {
-        boolean isRead   = false;
-        boolean isWrite  = false;
-        boolean truncate = false;
-        if (options != null) {
-            for (final OpenOption op : options) {
-                if (op instanceof StandardOpenOption) {
-                    switch ((StandardOpenOption) op) {
-                        case READ:              isRead   = true; break;
-                        case WRITE:             isWrite  = true; break;
-                        case CREATE_NEW:
-                        case TRUNCATE_EXISTING: truncate = true; break;
-                    }
-                }
+    public abstract ReadableByteChannel reader() throws IOException;
+
+    /**
+     * A factory that returns an existing channel <cite>as-is</cite>.
+     * The channel can be returned only once.
+     */
+    private static final class Stream extends ChannelFactory {
+        /**
+         * The channel, or {@code null} if it has already been returned.
+         */
+        private ReadableByteChannel input;
+
+        /**
+         * Creates a new factory for the given channel, which will be returned only once.
+         */
+        Stream(final ReadableByteChannel input) {
+            this.input = input;
+        }
+
+        /**
+         * Returns whether {@link #reader()} can be invoked.
+         */
+        @Override
+        public boolean canOpen() {
+            return input != null;
+        }
+
+        /**
+         * Returns the channel on the first invocation or
+         * throws an exception on all subsequent invocations.
+         */
+        @Override
+        public ReadableByteChannel reader() throws IOException {
+            final ReadableByteChannel in = input;
+            if (in != null) {
+                input = null;
+                return in;
             }
+            throw new IOException(Resources.format(Resources.Keys.StreamIsReadOnce));
         }
-        return isWrite & (!isRead | truncate);
     }
 
     /**
-     * Returns the {@link Resources.Keys} value together with the parameters given by {@code errorMessageParameters(…)}.
-     *
-     * @param   parameters  the result of {@code errorMessageParameters(…)} method call.
-     * @return  the {@link Resources.Keys} value to use for formatting the error message.
-     *
-     * @since 0.8
+     * A factory used as a fallback when we failed to convert a {@link File} to a {@link Path}.
+     * This is used only if the conversion attempt threw an {@link InvalidPathException}. Such
+     * failure is unlikely to happen, but if it happens anyway we try to open the channel in a
+     * way less surprising for the user (closer to the object he has specified).
      */
-    public static short errorMessageKey(final Object[] parameters) {
-        return (parameters.length == 2) ? Resources.Keys.CanNotReadFile_2 :
-               (parameters.length == 3) ? Resources.Keys.CanNotReadFile_3 :
-                                          Resources.Keys.CanNotReadFile_4;
+    private static final class Fallback extends ChannelFactory {
+        /**
+         * The file for which to open a channel.
+         */
+        private final File file;
+
+        /**
+         * The reason why we are using this fallback instead than a {@link Path}.
+         * Will be reported at most once, then set to {@code null}.
+         */
+        private InvalidPathException cause;
+
+        /**
+         * Creates a new fallback to use if the given file can not be converted to a {@link Path}.
+         */
+        Fallback(final File file, final InvalidPathException cause) {
+            this.file  = file;
+            this.cause = cause;
+        }
+
+        /**
+         * Opens a new input stream for the file given at construction time.
+         * The returned stream is <strong>not</strong> buffered.
+         *
+         * <p>On the first invocation, this method reports a warning about the failure to convert the
+         * {@link File} to a {@link Path}. On all subsequent invocations, the file is opened silently.</p>
+         */
+        @Override
+        public FileInputStream inputStream() throws IOException {
+            final FileInputStream in;
+            try {
+                in = new FileInputStream(file);
+            } catch (IOException ioe) {
+                if (cause != null) {
+                    ioe.addSuppressed(cause);
+                    cause = null;
+                }
+                throw ioe;
+            }
+            /*
+             * We have been able to create a channel, maybe not with the given OpenOptions.
+             * But the exception was nevertheless unexpected, so log its stack trace in order
+             * to allow the developer to check if there is something wrong.
+             */
+            if (cause != null) {
+                Logging.unexpectedException(Logging.getLogger(Modules.STORAGE), ChannelFactory.class, "prepare", cause);
+                cause = null;
+            }
+            return in;
+        }
+
+        /**
+         * Opens a new channel for the file given at construction time.
+         */
+        @Override
+        public ReadableByteChannel reader() throws IOException {
+            return inputStream().getChannel();
+        }
     }
 
     /**
-     * Returns the parameters for an error message saying that an error occurred while processing a file.
-     * This method uses the information provided by methods like {@link LineNumberReader#getLineNumber()}
-     * or {@link XMLStreamReader#getLocation()} if the given {@code store} is one of the supported types.
-     *
-     * @param  format    abbreviation of the file format (e.g. "CSV", "GML", "WKT", <i>etc</i>).
-     * @param  filename  name of the file or the data store.
-     * @param  store     the input or output object, or {@code null}.
-     * @return the parameters for a localized error message for a file that can not be processed.
-     *
-     * @since 0.8
+     * Invoked for reporting exceptions that may be normal behavior. This method logs
+     * the exception at {@link java.util.logging.Level#FINE} without stack trace.
      */
-    @SuppressWarnings("fallthrough")
-    public static Object[] errorMessageParameters(final String format, final String filename, final Object store) {
-        int line   = 0;
-        int column = 0;
-        if (store instanceof XMLStreamReader) {
-            final Location location = ((XMLStreamReader) store).getLocation();
-            line   = location.getLineNumber()   + 1;
-            column = location.getColumnNumber() + 1;
-        } else if (store instanceof LineNumberReader) {
-            line = ((LineNumberReader) store).getLineNumber();
-        }
-        final Object[] params = new Object[(line == 0) ? 2 : (column == 0) ? 3 : 4];
-        switch (params.length) {
-            default: // Fallthrough everywhere
-            case 4:  params[3] = column;
-            case 3:  params[2] = line;
-            case 2:  params[1] = filename;
-            case 1:  params[0] = format;
-            case 0:  break;
-        }
-        return params;
+    private static void recoverableException(final Exception warning) {
+        Logging.recoverableException(Logging.getLogger(Modules.STORAGE), ChannelFactory.class, "prepare", warning);
     }
 }

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/IOUtilities.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -16,15 +16,9 @@
  */
 package org.apache.sis.internal.storage;
 
-import java.util.Set;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Collections;
-import java.util.Arrays;
 import java.util.Locale;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.InputStream;
 import java.io.IOException;
 import java.io.LineNumberReader;
 import java.net.URI;
@@ -34,22 +28,16 @@ import java.net.URISyntaxException;
 import java.net.MalformedURLException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
 import java.nio.file.FileSystemNotFoundException;
 import java.nio.file.OpenOption;
 import java.nio.file.StandardOpenOption;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
 import java.nio.charset.StandardCharsets;
 import javax.xml.stream.Location;
 import javax.xml.stream.XMLStreamReader;
-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;
-import org.apache.sis.internal.system.Modules;
 
 
 /**
@@ -69,12 +57,6 @@ import org.apache.sis.internal.system.Mo
  */
 public final class IOUtilities extends Static {
     /**
-     * Options to be rejected by {@link #open(Object, String, OpenOption[])} for safety reasons.
-     */
-    private static final Set<StandardOpenOption> ILLEGAL_OPTIONS = EnumSet.of(
-            StandardOpenOption.APPEND, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DELETE_ON_CLOSE);
-
-    /**
      * Do not allow instantiation of this class.
      */
     private IOUtilities() {
@@ -403,159 +385,6 @@ public final class IOUtilities extends S
     }
 
     /**
-     * Returns a byte channel from the given input, or {@code null} if the input is of unknown type.
-     * More specifically:
-     *
-     * <ul>
-     *   <li>If the given input is {@code null}, then this method returns {@code null}.</li>
-     *   <li>If the given input is a {@link ReadableByteChannel} or an {@link InputStream},
-     *       then it is returned directly, or indirectly as a wrapper.</li>
-     *   <li>If the given input if a {@link Path}, {@link File}, {@link URL}, {@link URI}
-     *       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 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, OpenOption... options) throws IOException {
-        /*
-         * Unconditionally verify the options, even if we may not use them.
-         */
-        final Set<OpenOption> optionSet;
-        if (options == null || options.length == 0) {
-            optionSet = Collections.singleton(StandardOpenOption.READ);
-        } else {
-            optionSet = new HashSet<>(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) {
-            if (input instanceof FileInputStream) {
-                return ((FileInputStream) input).getChannel();
-            }
-            return Channels.newChannel((InputStream) input);
-        }
-        /*
-         * 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 = toPath((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(Resources.format(Resources.Keys.MissingSchemeInURI_1, uri));
-            } else try {
-                input = Paths.get(uri);
-            } catch (IllegalArgumentException | FileSystemNotFoundException e) {
-                try {
-                    input = uri.toURL();
-                } catch (MalformedURLException ioe) {
-                    ioe.addSuppressed(e);
-                    throw ioe;
-                }
-                // 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);
-            }
-            /*
-             * If the input is a File or a CharSequence that we have been able to convert to a File,
-             * try to convert to a Path in order to be able to use the OpenOptions. Only if we fail
-             * to convert to a Path (which is unlikely), we will use directly the File.
-             */
-            if (input instanceof File) {
-                try {
-                    input = ((File) input).toPath();
-                } catch (InvalidPathException e) {
-                    // Unlikely to happen. But if it happens anyway, try to open the channel in a
-                    // way less surprising for the user (closer to the object he has specified).
-                    final ReadableByteChannel channel;
-                    try {
-                        channel = new FileInputStream((File) input).getChannel();
-                    } catch (IOException ioe) {
-                        ioe.addSuppressed(e);
-                        throw ioe;
-                    }
-                    // We have been able to create a channel, maybe not with the given OpenOptions.
-                    // But the exception was nevertheless unexpected, so log its stack trace in order
-                    // to allow the developer to check if there is something wrong.
-                    Logging.unexpectedException(Logging.getLogger(Modules.STORAGE), IOUtilities.class, "open", e);
-                    return channel;
-                }
-            }
-        }
-        /*
-         * 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 Path) {
-            return Files.newByteChannel((Path) 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(Modules.STORAGE), IOUtilities.class, "open", warning);
-    }
-
-    /**
      * Returns {@code true} if the given options would open a file mostly for writing.
      * This method returns {@code true} if the following conditions are true:
      *

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -144,6 +144,11 @@ public final class Resources extends Ind
         public static final short StreamIsForwardOnly_1 = 13;
 
         /**
+         * Stream can be read only once.
+         */
+        public static final short StreamIsReadOnce = 18;
+
+        /**
          * Format of “{0}” is not recognized.
          */
         public static final short UnknownFormatFor_1 = 14;

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties [ISO-8859-1] Thu Jan  5 07:16:37 2017
@@ -35,4 +35,5 @@ InconsistentNameComponents_2      = Comp
 MissingSchemeInURI_1              = Missing scheme in \u201c{0}\u201d URI.
 ProcessingExecutedOn_1            = Processing executed on {0}.
 StreamIsForwardOnly_1             = Can not move backward in the \u201c{0}\u201d stream.
+StreamIsReadOnce                  = Stream can be read only once.
 UnknownFormatFor_1                = Format of \u201c{0}\u201d is not recognized.

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties [ISO-8859-1] Thu Jan  5 07:16:37 2017
@@ -40,4 +40,5 @@ InconsistentNameComponents_2      = Les
 MissingSchemeInURI_1              = Il manque le sch\u00e9ma dans l\u2019URI \u00ab\u202f{0}\u202f\u00bb.
 ProcessingExecutedOn_1            = Traitement ex\u00e9cut\u00e9 sur {0}.
 StreamIsForwardOnly_1             = Ne peut pas reculer dans le flux de donn\u00e9es \u00ab\u202f{0}\u202f\u00bb.
+StreamIsReadOnce                  = Ce flux de donn\u00e9es ne peut \u00eatre lu qu\u2019une seule fois.
 UnknownFormatFor_1                = Le format de \u00ab\u202f{0}\u202f\u00bb n\u2019est pas reconnu.

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -25,7 +25,7 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 package org.apache.sis.internal.storage;

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -42,6 +42,7 @@ import org.apache.sis.util.ArgumentCheck
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.storage.IOUtilities;
+import org.apache.sis.internal.storage.ChannelFactory;
 import org.apache.sis.internal.storage.ChannelDataInput;
 import org.apache.sis.internal.storage.ChannelImageInputStream;
 import org.apache.sis.setup.OptionKey;
@@ -73,7 +74,7 @@ import org.apache.sis.setup.OptionKey;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 public class StorageConnector implements Serializable {
@@ -397,6 +398,12 @@ public class StorageConnector implements
             } else if (type == ChannelDataInput.class) {                // Undocumented case (SIS internal)
                 createChannelDataInput(false);
                 done = true;
+            } else if (type == ChannelFactory.class) {                  // Undocumented case (SIS internal)
+                /*
+                 * ChannelFactory may have been created as a side effect of creating a ReadableByteChannel.
+                 * Caller should have asked for another type (e.g. InputStream) before to ask for this type.
+                 */
+                done = true;
             }
         } catch (IOException e) {
             throw new DataStoreException(Errors.format(Errors.Keys.CanNotOpen_1, getStorageName()), e);
@@ -455,11 +462,12 @@ public class StorageConnector implements
          * Following method call recognizes ReadableByteChannel, InputStream (with special case for FileInputStream),
          * URL, URI, File, Path or other types that may be added in future SIS versions.
          */
-        final ReadableByteChannel channel = IOUtilities.open(storage,
+        final ChannelFactory factory = ChannelFactory.prepare(storage,
                 getOption(OptionKey.URL_ENCODING), getOption(OptionKey.OPEN_OPTIONS));
 
         ChannelDataInput asDataInput = null;
-        if (channel != null) {
+        if (factory != null) {
+            final ReadableByteChannel channel = factory.reader();
             addViewToClose(channel, storage);
             ByteBuffer buffer = getOption(OptionKey.BYTE_BUFFER);
             if (buffer == null) {
@@ -474,6 +482,13 @@ public class StorageConnector implements
                 asDataInput = new ChannelDataInput(name, channel, buffer, false);
             }
             addViewToClose(asDataInput, channel);
+            /*
+             * Following is an undocumented mechanism for allowing some Apache SIS implementations of DataStore
+             * to re-open the same channel or input stream another time, typically for re-reading the same data.
+             */
+            if (factory.canOpen()) {
+                addView(ChannelFactory.class, factory);
+            }
         }
         addView(ChannelDataInput.class, asDataInput);
     }

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -27,7 +27,7 @@
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.4
+ * @version 0.8
  * @module
  */
 package org.apache.sis.storage;

Modified: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxDataStore.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxDataStore.java?rev=1777424&r1=1777423&r2=1777424&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxDataStore.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxDataStore.java [UTF-8] Thu Jan  5 07:16:37 2017
@@ -37,6 +37,7 @@ import org.apache.sis.setup.OptionKey;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.internal.storage.ChannelFactory;
 import org.apache.sis.internal.storage.FeatureStore;
 import org.apache.sis.internal.storage.Markable;
 import org.apache.sis.internal.util.AbstractMap;
@@ -46,7 +47,7 @@ import org.apache.sis.util.logging.Warni
 
 
 /**
- * Base class of XML data stores based on the STAX framework.
+ * Base class of XML data stores based on the StAX framework.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -78,7 +79,7 @@ public abstract class StaxDataStore exte
     protected final Charset encoding;
 
     /**
-     * Configuration information for JAXB (un)marshaller (actually the SIS wrappers) or for the STAX factories.
+     * Configuration information for JAXB (un)marshaller (actually the SIS wrappers) or for the StAX factories.
      * This object is a read-only map which may contain the following entries:
      *
      * <ul>
@@ -87,7 +88,7 @@ public abstract class StaxDataStore exte
      * </ul>
      *
      * In addition, the {@link Config} class also implements various listener interfaces to be given to
-     * JAXB (un)marshallers (actually the SIS wrappers) and STAX factories configuration.
+     * JAXB (un)marshallers (actually the SIS wrappers) and StAX factories configuration.
      *
      * @see OptionKey#LOCALE
      * @see OptionKey#TIMEZONE
@@ -97,7 +98,7 @@ public abstract class StaxDataStore exte
     /**
      * The storage object given by the user. May be {@link Path}, {@link java.net.URL}, {@link InputStream},
      * {@link java.io.OutputStream}, {@link java.io.Reader}, {@link java.io.Writer}, {@link XMLStreamReader},
-     * {@link XMLStreamWriter}, {@link org.w3c.dom.Node} or some other types that the STAX framework can handle.
+     * {@link XMLStreamWriter}, {@link org.w3c.dom.Node} or some other types that the StAX framework can handle.
      *
      * <p>A {@code null} value means that this datastore has been {@linkplain #close() closed}.</p>
      *
@@ -111,6 +112,10 @@ public abstract class StaxDataStore exte
      * For example if {@code storage} is a {@link java.nio.file.Path}, then {@code stream} will be some
      * stream or channel opened for that path.
      *
+     * <p>This reference should never be changed until the data store is closed. In particular, this field
+     * should <strong>not</strong> be set to the value of {@link ChannelFactory#inputStream()} because the
+     * later does not create the same kind of input stream than {@link StorageConnector}.</p>
+     *
      * @see #close()
      */
     private AutoCloseable stream;
@@ -135,20 +140,26 @@ public abstract class StaxDataStore exte
     private final OutputType storageToWriter;
 
     /**
-     * The STAX readers factory, created when first needed.
+     * The StAX readers factory, created when first needed.
      *
      * @see #inputFactory()
      */
     private XMLInputFactory inputFactory;
 
     /**
-     * The STAX writers factory, created when first needed.
+     * The StAX writers factory, created when first needed.
      *
      * @see #outputFactory()
      */
     private XMLOutputFactory outputFactory;
 
     /**
+     * Object to use for creating new input streams if we need to read the same data more than once.
+     * This field is {@code null} if we can not re-open new input streams.
+     */
+    private final ChannelFactory channelFactory;
+
+    /**
      * Whether the {@linkplain #stream} is currently in use by a {@link StaxStreamReader}.
      * Value can be one of {@link #READY}, {@link #IN_USE} or {@link #FINISHED} constants.
      */
@@ -210,11 +221,12 @@ public abstract class StaxDataStore exte
         } else {
             streamPosition = -1;
         }
+        channelFactory = connector.getStorageAs(ChannelFactory.class);              // Must be last.
     }
 
     /**
      * Holds information that can be used for (un)marshallers configuration, and opportunistically
-     * implement various listeners used by JAXB (actually the SIS wrappers) or STAX.
+     * implement various listeners used by JAXB (actually the SIS wrappers) or StAX.
      */
     private final class Config extends AbstractMap<String,Object> implements XMLReporter, WarningListener<Object> {
         /**
@@ -251,7 +263,7 @@ public abstract class StaxDataStore exte
         }
 
         /**
-         * Forwards STAX warnings to {@link DataStore} listeners.
+         * Forwards StAX warnings to {@link DataStore} listeners.
          * This method is invoked by {@link XMLStreamReader} when needed.
          *
          * @param message    the message to put in a logging record.
@@ -303,7 +315,7 @@ public abstract class StaxDataStore exte
     public abstract String getFormatName();
 
     /**
-     * Returns the factory for STAX readers. The same instance is returned for all {@code StaxDataStore} lifetime.
+     * Returns the factory for StAX readers. The same instance is returned for all {@code StaxDataStore} lifetime.
      * Warnings emitted by readers created by this factory will be forwarded to the {@link #listeners}.
      *
      * <p>This method is indirectly invoked by {@link #createReader(StaxStreamReader)},
@@ -319,7 +331,7 @@ public abstract class StaxDataStore exte
     }
 
     /**
-     * Returns the factory for STAX writers. The same instance is returned for all {@code StaxDataStore} lifetime.
+     * Returns the factory for StAX writers. The same instance is returned for all {@code StaxDataStore} lifetime.
      *
      * <p>This method is indirectly invoked by {@link #createWriter(StaxStreamWriter)},
      * through a call to {@link OutputType#create(StaxDataStore, Object)}.</p>
@@ -373,6 +385,7 @@ reset:  switch (state) {
             default: {
                 throw new AssertionError(state);
             }
+            case READY: break;                  // Stream already at the data start; nothing to do.
             case FINISHED: {
                 if (streamPosition >= 0) {
                     final Markable m = (Markable) input;
@@ -386,11 +399,21 @@ reset:  switch (state) {
                 }
                 // Failed to reset the stream - fallthrough.
             }
+            /*
+             * If the input stream is in use, or if we finished to use it but were unable to reset its position,
+             * then we need to create a new input stream (except if the input was a DOM in memory, which we can
+             * share).
+             */
             case IN_USE: {
-                // TODO: create a new stream here if we can.
-                throw new DataStoreException("Can not read twice.");
+                if (type == InputType.NODE) break;
+                if (channelFactory == null) {
+                    throw new DataStoreException("Can not read twice.");
+                }
+                final InputStream in = channelFactory.inputStream();
+                final XMLStreamReader reader = InputType.STREAM.create(this, in);
+                target.stream = in;
+                return reader;
             }
-            case READY: break;                      // Stream already at the data start; nothing to do.
         }
         final XMLStreamReader reader = type.create(this, input);
         target.stream = stream;



Mime
View raw message