sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1809448 - in /sis/branches/JDK8: core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/ storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/ storage/sis-storage/src/main/java/org/apache/sis/internal/stor...
Date Sat, 23 Sep 2017 19:49:34 GMT
Author: desruisseaux
Date: Sat Sep 23 19:49:33 2017
New Revision: 1809448

URL: http://svn.apache.org/viewvc?rev=1809448&view=rev
Log:
First draft of a capability to rewind a DataStore for text file, in order to read the file
more than once.

Added:
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/RewindableLineReader.java
  (with props)
    sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/RewindableLineReaderTest.java
  (with props)
Modified:
    sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/InputStreamAdapter.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Markable.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
    sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java

Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java?rev=1809448&r1=1809447&r2=1809448&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -238,7 +238,7 @@ public class DefaultMaintenanceInformati
     @Dependencies("getMaintenanceDates")
     public Date getDateOfNextUpdate() {
         final Collection<CitationDate> dates = getMaintenanceDates();
-        if (dates != null) { // May be null on XML marshalling.
+        if (dates != null) {                                                    // May be
null on XML marshalling.
             for (final CitationDate date : dates) {
                 if (DateType.NEXT_UPDATE.equals(date.getDateType())) {
                     return date.getDate();

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java?rev=1809448&r1=1809447&r2=1809448&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -46,6 +46,7 @@ import org.apache.sis.internal.referenci
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.internal.storage.io.IOUtilities;
+import org.apache.sis.internal.storage.io.RewindableLineReader;
 import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.MovingFeature;
 import org.apache.sis.internal.storage.Resources;
@@ -64,6 +65,7 @@ import org.apache.sis.setup.OptionKey;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.io.InvalidSeekException;
 import org.apache.sis.measure.Units;
 
 // Branch-dependent imports
@@ -238,7 +240,7 @@ final class Store extends URIDataStore i
             throw new UnsupportedStorageException(super.getLocale(), StoreProvider.NAME,
                     connector.getStorage(), connector.getOption(OptionKey.OPEN_OPTIONS));
         }
-        source = (r instanceof BufferedReader) ? (BufferedReader) r : new LineNumberReader(r);
+        source     = (r instanceof BufferedReader) ? (BufferedReader) r : new LineNumberReader(r);
         geometries = Geometries.implementation(connector.getOption(OptionKey.GEOMETRY_LIBRARY));
         dissociate = immediate;
         GeneralEnvelope envelope    = null;
@@ -246,7 +248,7 @@ final class Store extends URIDataStore i
         Foliation       foliation   = null;
         try {
             final List<String> elements = new ArrayList<>();
-            source.mark(1024);
+            source.mark(RewindableLineReader.BUFFER_SIZE);
             String line;
             while ((line = source.readLine()) != null) {
                 line = line.trim();
@@ -292,9 +294,9 @@ final class Store extends URIDataStore i
                     }
                 }
                 elements.clear();
-                source.mark(1024);
+                source.mark(RewindableLineReader.BUFFER_SIZE);
             }
-            source.reset();
+            source.reset();                 // Restore position to the first line after the
header.
         } catch (IOException e) {
             throw new DataStoreException(getLocale(), StoreProvider.NAME, super.getDisplayName(),
source).initCause(e);
         } catch (FactoryException e) {
@@ -310,6 +312,33 @@ final class Store extends URIDataStore i
     }
 
     /**
+     * Moves the reader position to beginning of file, if possible. We try to use the mark
defined by the constructor,
+     * which is set after the last header line. If the mark is no longer valid, then we have
to create a new line reader.
+     * In this later case, we have to skip the header lines (i.e. we reproduce the constructor
loop, but without parsing
+     * metadata).
+     */
+    private void rewind() throws IOException {
+        final BufferedReader reader = source;
+        if (!(reader instanceof RewindableLineReader)) {
+            throw new InvalidSeekException(Resources.forLocale(getLocale())
+                    .getString(Resources.Keys.StreamIsForwardOnly_1, getDisplayName()));
+        }
+        source = ((RewindableLineReader) reader).rewind();
+        if (source != reader) {
+            String line;
+            while ((line = source.readLine()) != null) {
+                line = line.trim();
+                if (!line.isEmpty()) {
+                    final char c = line.charAt(0);
+                    if (c != COMMENT && c != METADATA) break;
+                }
+                source.mark(RewindableLineReader.BUFFER_SIZE);
+            }
+            source.reset();         // Restore position to the first line after the header.
+        }
+    }
+
+    /**
      * Parses the envelope described by the header line starting with {@code @stboundedby}.
      * The envelope returned by this method will be stored in the {@link #envelope} field.
      *
@@ -657,7 +686,7 @@ final class Store extends URIDataStore i
      * @return a stream over all features in the CSV file.
      * @throws DataStoreException if an error occurred while creating the feature stream.
      *
-     * @todo Needs to reset the position when doing another pass on the features.
+     * @todo Need to reset the position when doing another pass on the features. See {@link
#rewind()}.
      * @todo If sequential order, publish Feature as soon as identifier changed.
      */
     @Override

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java?rev=1809448&r1=1809447&r2=1809448&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -28,7 +28,6 @@ import java.nio.file.Files;
 import java.nio.file.DirectoryStream;
 import java.nio.file.DirectoryIteratorException;
 import java.io.IOException;
-import java.io.UncheckedIOException;
 import java.util.logging.Level;
 import java.util.concurrent.ConcurrentHashMap;
 import org.opengis.metadata.Metadata;
@@ -48,6 +47,9 @@ import org.apache.sis.internal.util.Unmo
 import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.internal.storage.Resources;
 
+// Branch-dependent imports
+import java.io.UncheckedIOException;
+
 
 /**
  * A folder store acts as an aggregate of multiple files in a single store.

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java?rev=1809448&r1=1809447&r2=1809448&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelFactory.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -57,14 +57,14 @@ import org.apache.sis.storage.ForwardOnl
  * 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 reader(…)} method can be invoked an arbitrary amount
of times for creating as many
+ * {@link URL}, the {@link #readable readable(…)} 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 reader(…)} method invocation will succeed
and all subsequent ones
+ * In such case, only the first {@link #readable readable(…)} method invocation will succeed
and all subsequent ones
  * will throw an exception.
  *
  * <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
reader(…)}, <i>etc</i>).
+ * Callers are responsible for synchronizing their call to any member methods ({@link #readable
readable(…)}, <i>etc</i>).
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
@@ -230,10 +230,10 @@ public abstract class ChannelFactory {
         if (storage instanceof URL) {
             final URL file = (URL) storage;
             return new ChannelFactory() {
-                @Override public ReadableByteChannel reader(String filename, WarningListeners<DataStore>
listeners) throws IOException {
+                @Override public ReadableByteChannel readable(String filename, WarningListeners<DataStore>
listeners) throws IOException {
                     return Channels.newChannel(file.openStream());
                 }
-                @Override public WritableByteChannel writer(String filename, WarningListeners<DataStore>
listeners) throws IOException {
+                @Override public WritableByteChannel writable(String filename, WarningListeners<DataStore>
listeners) throws IOException {
                     return Channels.newChannel(file.openConnection().getOutputStream());
                 }
             };
@@ -242,10 +242,10 @@ public abstract class ChannelFactory {
             final Path path = (Path) storage;
             if (Files.isRegularFile(path)) {
                 return new ChannelFactory() {
-                    @Override public ReadableByteChannel reader(String filename, WarningListeners<DataStore>
listeners) throws IOException {
+                    @Override public ReadableByteChannel readable(String filename, WarningListeners<DataStore>
listeners) throws IOException {
                         return Files.newByteChannel(path, optionSet);
                     }
-                    @Override public WritableByteChannel writer(String filename, WarningListeners<DataStore>
listeners) throws IOException {
+                    @Override public WritableByteChannel writable(String filename, WarningListeners<DataStore>
listeners) throws IOException {
                         return Files.newByteChannel(path, optionSet);
                     }
                 };
@@ -267,10 +267,11 @@ public abstract class ChannelFactory {
     }
 
     /**
-     * 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 reader(…)}
has already been invoked.
+     * Returns {@code true} if this factory is capable to create another readable byte channel.
+     * This method returns {@code false} if this factory is capable to create only one channel
+     * and {@link #readable readable(…)} has already been invoked.
      *
-     * @return whether {@link #reader reader(…)} or {@link #writer writer(…)} can be
invoked.
+     * @return whether {@link #readable readable(…)} or {@link #writable writable(…)}
can be invoked.
      */
     public boolean canOpen() {
         return true;
@@ -289,7 +290,7 @@ public abstract class ChannelFactory {
     public InputStream inputStream(String filename, WarningListeners<DataStore> listeners)
             throws DataStoreException, IOException
     {
-        return Channels.newInputStream(reader(filename, listeners));
+        return Channels.newInputStream(readable(filename, listeners));
     }
 
     /**
@@ -305,7 +306,7 @@ public abstract class ChannelFactory {
     public OutputStream outputStream(String filename, WarningListeners<DataStore> listeners)
             throws DataStoreException, IOException
     {
-        return Channels.newOutputStream(writer(filename, listeners));
+        return Channels.newOutputStream(writable(filename, listeners));
     }
 
     /**
@@ -319,7 +320,7 @@ public abstract class ChannelFactory {
      * @throws DataStoreException if the channel is read-once.
      * @throws IOException if an error occurred while opening the channel.
      */
-    public abstract ReadableByteChannel reader(String filename, WarningListeners<DataStore>
listeners)
+    public abstract ReadableByteChannel readable(String filename, WarningListeners<DataStore>
listeners)
             throws DataStoreException, IOException;
 
     /**
@@ -333,7 +334,7 @@ public abstract class ChannelFactory {
      * @throws DataStoreException if the channel is write-once.
      * @throws IOException if an error occurred while opening the channel.
      */
-    public abstract WritableByteChannel writer(String filename, WarningListeners<DataStore>
listeners)
+    public abstract WritableByteChannel writable(String filename, WarningListeners<DataStore>
listeners)
             throws DataStoreException, IOException;
 
     /**
@@ -362,7 +363,7 @@ public abstract class ChannelFactory {
         }
 
         /**
-         * Returns whether {@link #reader reader(…)} or {@link #writer writer(…)} can
be invoked.
+         * Returns whether {@link #readable readable(…)} or {@link #writable writable(…)}
can be invoked.
          */
         @Override
         public boolean canOpen() {
@@ -374,7 +375,7 @@ public abstract class ChannelFactory {
          * throws an exception on all subsequent invocations.
          */
         @Override
-        public ReadableByteChannel reader(final String filename, final WarningListeners<DataStore>
listeners)
+        public ReadableByteChannel readable(final String filename, final WarningListeners<DataStore>
listeners)
                 throws DataStoreException, IOException
         {
             final Channel in = channel;
@@ -396,7 +397,7 @@ public abstract class ChannelFactory {
          * throws an exception on all subsequent invocations.
          */
         @Override
-        public WritableByteChannel writer(final String filename, final WarningListeners<DataStore>
listeners)
+        public WritableByteChannel writable(final String filename, final WarningListeners<DataStore>
listeners)
                 throws DataStoreException, IOException
         {
             final Channel out = channel;
@@ -520,7 +521,7 @@ public abstract class ChannelFactory {
          * Opens a new channel for the file given at construction time.
          */
         @Override
-        public ReadableByteChannel reader(String filename, WarningListeners<DataStore>
listeners) throws IOException {
+        public ReadableByteChannel readable(String filename, WarningListeners<DataStore>
listeners) throws IOException {
             return inputStream(filename, listeners).getChannel();
         }
 
@@ -528,7 +529,7 @@ public abstract class ChannelFactory {
          * Opens a new channel for the file given at construction time.
          */
         @Override
-        public WritableByteChannel writer(String filename, WarningListeners<DataStore>
listeners) throws IOException {
+        public WritableByteChannel writable(String filename, WarningListeners<DataStore>
listeners) throws IOException {
             return outputStream(filename, listeners).getChannel();
         }
     }

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/InputStreamAdapter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/InputStreamAdapter.java?rev=1809448&r1=1809447&r2=1809448&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/InputStreamAdapter.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/InputStreamAdapter.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -27,6 +27,14 @@ import java.io.UncheckedIOException;
 /**
  * Wraps an {@link ImageInputStream} as a standard {@link InputStream}.
  *
+ * <div class="section">Thread-safety</div>
+ * This class is thread-safe only if the underlying {@link ImageInputStream} is itself thread-safe.
+ * For performance reasons, this class does not synchronize the frequently invoked {@code
read(…)}
+ * methods since they do nothing else than delegating to {@code ImageInputStream}. This means
that
+ * if the wrapped input is {@link ChannelImageInputStream}, then this class is <strong>not</strong>
+ * thread-safe. This is not necessarily a contradiction with Java API since input streams
define no
+ * explicit synchronization lock (contrarily to {@link java.io.Reader}.
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 0.8
  *
@@ -56,6 +64,13 @@ public final class InputStreamAdapter ex
     private int nestedMarks;
 
     /**
+     * Temporarily set to {@code true} if a call to {@link #close()} should not be propagated
to the {@link #input}.
+     *
+     * @see RewindableLineReader#rewind()
+     */
+    boolean keepOpen;
+
+    /**
      * Constructs a new input stream.
      *
      * @param  input  the stream to wrap.
@@ -130,7 +145,7 @@ public final class InputStreamAdapter ex
      * @throws UncheckedIOException if the mark can not be set.
      */
     @Override
-    public void mark(final int readlimit) {
+    public synchronized void mark(final int readlimit) {
         try {
             markPosition = input.getStreamPosition();
             input.flushBefore(markPosition);
@@ -146,7 +161,7 @@ public final class InputStreamAdapter ex
      * It is okay to invoke this method after {@link #mark(int)} (but not before).
      */
     @Override
-    public void mark() {
+    public synchronized void mark() {
         input.mark();
         nestedMarks++;
     }
@@ -170,7 +185,7 @@ public final class InputStreamAdapter ex
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public void reset() throws IOException {
+    public synchronized void reset() throws IOException {
         if (--nestedMarks >= 0) {
             input.reset();
         } else {
@@ -195,7 +210,7 @@ public final class InputStreamAdapter ex
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public void close() throws IOException {
-        input.close();
+    public synchronized void close() throws IOException {
+        if (!keepOpen) input.close();
     }
 }

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Markable.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Markable.java?rev=1809448&r1=1809447&r2=1809448&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Markable.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Markable.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -36,7 +36,10 @@ import java.io.IOException;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
- * @since   0.8
+ *
+ * @see Readable
+ *
+ * @since 0.8
  * @module
  */
 public interface Markable {

Added: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/RewindableLineReader.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/RewindableLineReader.java?rev=1809448&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/RewindableLineReader.java
(added)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/RewindableLineReader.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -0,0 +1,144 @@
+/*
+ * 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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.nio.charset.Charset;
+import org.apache.sis.io.InvalidSeekException;
+
+
+/**
+ * A {@link LineNumberReader} which may be rewinded to its original position even if the
mark is no longer valid.
+ * This class assumes that {@link #mark(int)} has not been invoked, or has been invoked at
the position where to
+ * rewind. A call to {@link #rewind()} performs the following actions:
+ *
+ * <ul>
+ *   <li>Attempt to call {@link #reset()}.</li>
+ *   <li>If {@code reset()} failed, then attempt to seek the input stream to its original
position and create a new reader.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ */
+public final class RewindableLineReader extends LineNumberReader {
+    /**
+     * Size of the buffer, in number of characters.
+     */
+    public static final int BUFFER_SIZE = 8192;
+
+    /**
+     * The input stream, or {@code null} if this reader can not rewind anymore.
+     *
+     * <div class="note"><b>Note:</b> we do not use the more generic {@link
java.io.InputStream} class
+     * because this whole {@code ReaderFactory} class is useless if we can not seek in this
stream.</div>
+     */
+    private InputStreamAdapter input;
+
+    /**
+     * The character encoding, or {@code null} for the platform default.
+     */
+    private final Charset encoding;
+
+    /**
+     * Creates a line reader wrapping the given input stream.
+     *
+     * @param  input     the input stream from which to read characters.
+     * @param  encoding  the character encoding, or {@code null} for the platform default.
+     * @throws IOException if an error occurred while marking the reader.
+     */
+    public RewindableLineReader(final InputStream input, final Charset encoding) throws IOException
{
+        super(encoding != null ? new InputStreamReader(input, encoding)
+                               : new InputStreamReader(input), BUFFER_SIZE);
+        if (input instanceof InputStreamAdapter) {
+            this.input = (InputStreamAdapter) input;
+        }
+        this.encoding = encoding;
+        super.mark(BUFFER_SIZE);
+        /*
+         * By default, this.lock is set to InputStreamReader. But InputStreamReader.lock
has itself
+         * been set on the given InputStreamAdapter.  So we set this.lock to InputStreamReader.lock
+         * in order to have a single synchronization lock.
+         */
+        lock = input;
+    }
+
+    /**
+     * Returns a reader rewinded to the beginning of data to read. This method invokes {@link
#reset()} first.
+     * If that call succeed, then this method returns {@code this}. Otherwise this method
returns a new reader.
+     * In the later case, {@code this} reader should not be used anymore.
+     *
+     * @return the reader to use for next read operation (may be {@code this}).
+     * @throws IOException if an error occurred while rewinding the reader.
+     */
+    @SuppressWarnings("SynchronizeOnNonFinalField")
+    public RewindableLineReader rewind() throws IOException {
+        synchronized (lock) {
+            try {
+                reset();
+                return this;
+            } catch (IOException e1) {
+                final InputStreamAdapter stream = input;
+                if (stream == null) {
+                    throw new InvalidSeekException();
+                }
+                /*
+                 * Releases the resources used by this reader, but without closing the underlying
input stream.
+                 * This reader is considered closed after this method call, but the underlying
input stream can
+                 * still be given to another reader.
+                 */
+                input = null;
+                assert Thread.holdsLock(stream);    // Should be okay since we set lock =
stream in constructor.
+                try {
+                    stream.keepOpen = true;
+                    super.close();
+                } finally {
+                    stream.keepOpen = false;
+                }
+                /*
+                 * Try to seek to the data origin. Note that 'seek(0)' below does not necessarily
+                 * move to the beginning of file, since ChannelDataInput may contain an offset.
+                 */
+                try {
+                    stream.input.seek(0);
+                } catch (IOException e2) {
+                    e2.addSuppressed(e1);
+                    throw e2;
+                }
+                return new RewindableLineReader(stream, encoding);
+            }
+        }
+    }
+
+    /**
+     * Closes this reader.
+     *
+     * @throws IOException if an error occurred while closing the reader.
+     */
+    @Override
+    @SuppressWarnings("SynchronizeOnNonFinalField")
+    public void close() throws IOException {
+        synchronized (lock) {
+            input = null;
+            super.close();
+        }
+    }
+}

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/RewindableLineReader.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/RewindableLineReader.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

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=1809448&r1=1809447&r2=1809448&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] Sat Sep 23 19:49:33 2017
@@ -29,7 +29,6 @@ import java.io.InputStreamReader;
 import java.io.BufferedInputStream;
 import java.io.Serializable;
 import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
 import java.nio.channels.Channel;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.SeekableByteChannel;
@@ -54,6 +53,7 @@ import org.apache.sis.internal.storage.i
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.storage.io.ChannelImageInputStream;
 import org.apache.sis.internal.storage.io.InputStreamAdapter;
+import org.apache.sis.internal.storage.io.RewindableLineReader;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Utilities;
 import org.apache.sis.io.InvalidSeekException;
@@ -98,6 +98,11 @@ public class StorageConnector implements
     /**
      * The default size of the {@link ByteBuffer} to be created.
      * Users can override this value by providing a value for {@link OptionKey#BYTE_BUFFER}.
+     * Note that it usually don't need to be very large, since {@link RewindableLineReader}
+     * will have its own buffer (which may be larger) and {@link ChannelDataInput} methods
+     * writing in existing destination arrays will bypass the buffer.
+     *
+     * @see RewindableLineReader#BUFFER_SIZE
      */
     static final int DEFAULT_BUFFER_SIZE = 4096;
 
@@ -918,7 +923,7 @@ public class StorageConnector implements
          * (potentially an InputStream). We need to remember this chain in 'Coupled' objects.
          */
         final String name = getStorageName();
-        final ReadableByteChannel channel = factory.reader(name, null);
+        final ReadableByteChannel channel = factory.readable(name, null);
         addView(ReadableByteChannel.class, channel, null, factory.isCoupled() ? CASCADE_ON_RESET
: 0);
         ByteBuffer buffer = getOption(OptionKey.BYTE_BUFFER);       // User-supplied buffer.
         if (buffer == null) {
@@ -1148,10 +1153,7 @@ public class StorageConnector implements
             return null;
         }
         input.mark(DEFAULT_BUFFER_SIZE);
-        final Charset encoding = getOption(OptionKey.ENCODING);
-        Reader in = (encoding != null) ? new InputStreamReader(input, encoding)
-                                       : new InputStreamReader(input);
-        in = new LineNumberReader(in);
+        final Reader in = new RewindableLineReader(input, getOption(OptionKey.ENCODING));
         addView(Reader.class, in, InputStream.class, (byte) (CLEAR_ON_RESET | CASCADE_ON_RESET));
         return in;
     }

Added: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/RewindableLineReaderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/RewindableLineReaderTest.java?rev=1809448&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/RewindableLineReaderTest.java
(added)
+++ sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/RewindableLineReaderTest.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -0,0 +1,128 @@
+/*
+ * 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.io;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.imageio.stream.ImageInputStreamImpl;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link RewindableLineReader}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ */
+public final strictfp class RewindableLineReaderTest extends TestCase {
+    /**
+     * Number of bytes to transfer from the {@code ImageInputStream} to {@code BufferedReader}.
+     * We use a small size in order to invalidate the {@code BufferedReader} mark without
having
+     * to fill all the buffer.
+     */
+    private static final int TRANSFERT_SIZE = 100;
+
+    /**
+     * Tests {@link RewindableLineReader#rewind()}.
+     *
+     * @throws IOException if an error occurred while reading characters.
+     */
+    @Test
+    public void testRewind() throws IOException {
+        RewindableLineReader reader = reader();
+        reader.mark(TRANSFERT_SIZE);                    // Use a smaller limit for testing
sooner mark invalidation.
+        assertEquals("charAt(0)", 'A', reader.read());
+        assertEquals("charAt(1)", 'B', reader.read());
+        assertEquals("charAt(2)", 'C', reader.read());
+        /*
+         * Since we have read less than 100 characters, RewindableLineReader.rewind()
+         * should be able to successfully delegate the work to BufferedReader.reset().
+         */
+        assertSame("BufferedReader.reset() should have succeeded.", reader, reader.rewind());
+        assertEquals("charAt(0)", 'A', reader.read());
+        assertEquals("charAt(1)", 'B', reader.read());
+        assertEquals("charAt(2)", 'C', reader.read());
+        assertEquals("charAt(3)", 'D', reader.read());
+        /*
+         * Skip a number of characters greater than the current buffer content. It should
cause BufferedReader to
+         * invalidate the mark. As a result of failure to execute BufferedReader.reset(),
the 'reader' variable
+         * should get a new value. However while we expect the value to change, we do not
require it since whether
+         * BufferedReader.reset() succeeded or not depends on BufferedReader implementation.
+         */
+        reader.skip(2 * TRANSFERT_SIZE);
+        assertEquals("charAt(…)", 'W', reader.read());
+        final RewindableLineReader old = reader;
+        reader = reader.rewind();                       // Should be a new instance, but
this is not mandatory.
+        if (reader != old) try {
+            old.read();
+            fail("Old reader should be closed.");
+        } catch (IOException e) {
+            // This is the expected exception.
+        }
+        assertEquals("charAt(0)", 'A', reader.read());
+        assertEquals("charAt(1)", 'B', reader.read());
+        assertEquals("charAt(2)", 'C', reader.read());
+        assertEquals("charAt(3)", 'D', reader.read());
+        reader.close();
+    }
+
+    /**
+     * Returns a reader over a dummy sequence of characters. That reader returns letters
+     * from A to Z, than restart that sequence an infinite number of times.
+     */
+    private static RewindableLineReader reader() throws IOException {
+        return new RewindableLineReader(new InputStreamAdapter(new ImageInputStreamImpl()
{
+            /** Next byte to return, in A … Z range. */
+            private char next = 'A';
+
+            /** Returns the next byte in A … Z range. */
+            @Override public int read() {
+                final char c = next;
+                if (++next > 'Z') {
+                    next = 'A';
+                }
+                return c;
+            }
+
+            /**
+             * Transfers at most {@value #TRANSFERT_SIZE} bytes. We put a limit in the number
of bytes
+             * to be transfered in order to cause {@link BufferedReader} to invalidate the
mark sooner
+             * than waiting that we have filled the buffer.
+             */
+            @Override public int read(final byte[] buffer, int offset, int length) {
+                if (length > TRANSFERT_SIZE) {
+                    length = TRANSFERT_SIZE;
+                }
+                for (int i=0; i<length; i++) {
+                    buffer[offset++] = (byte) read();
+                }
+                return length;
+            }
+
+            /** The only seek allowed for this test should be at the beginning of stream.
*/
+            @Override public void seek(final long pos) throws IOException {
+                assertEquals("Should seek at origin.", 0, pos);
+                next = 'A';
+            }
+        }), StandardCharsets.US_ASCII);
+    }
+}

Propchange: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/io/RewindableLineReaderTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Modified: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java?rev=1809448&r1=1809447&r2=1809448&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
[UTF-8] Sat Sep 23 19:49:33 2017
@@ -37,6 +37,7 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.storage.io.ChannelImageInputStreamTest.class,
     org.apache.sis.internal.storage.io.ChannelImageOutputStreamTest.class,
     org.apache.sis.internal.storage.io.HyperRectangleReaderTest.class,
+    org.apache.sis.internal.storage.io.RewindableLineReaderTest.class,
     org.apache.sis.internal.storage.MetadataBuilderTest.class,
     org.apache.sis.storage.FeatureNamingTest.class,
     org.apache.sis.storage.ProbeResultTest.class,



Mime
View raw message