You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2017/09/23 19:49:34 UTC
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...
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,