blob: fa63a556dd40eb48d25e9b7832b6e9daf756185b [file] [log] [blame]
/*
* 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.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import javax.imageio.stream.IIOByteBuffer;
import javax.imageio.stream.ImageInputStream;
/**
* Adds the missing methods in {@code ChannelDataInput} for implementing the {@code ImageInputStream} interface.
* The JDK approach for creating an image input stream from a channel would be as below:
*
* {@preformat java
* ReadableByteChannel channel = ...;
* ImageInputStream stream = ImageIO.createImageInputStream(Channels.newInputStream(channel));
* }
*
* However the standard {@link javax.imageio.stream.ImageInputStreamImpl} implementation performs many work by itself,
* including supporting various {@linkplain ByteOrder byte order}, which could be more efficiently done by NIO.
* Furthermore, this class allows us to reuse an existing buffer (especially direct buffer, which are costly to create)
* and allow subclasses to store additional information, for example the file path.
*
* <p>This class is used when compatibility with {@link javax.imageio.ImageReader} is needed.</p>
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.5
*
* @see javax.imageio.stream.FileImageInputStream
* @see javax.imageio.ImageIO#createImageInputStream(Object)
* @see java.nio.channels.Channels#newInputStream(ReadableByteChannel)
*
* @since 0.3
* @module
*/
public class ChannelImageInputStream extends ChannelDataInput implements ImageInputStream {
/**
* Creates a new input stream for the given channel and using the given buffer.
*
* @param filename a file identifier used only for formatting error message.
* @param channel the channel from where data are read.
* @param buffer the buffer where to copy the data.
* @param filled {@code true} if the buffer already contains data, or {@code false} if it needs
* to be initially filled with some content read from the channel.
* @throws IOException if an error occurred while reading the channel.
*/
public ChannelImageInputStream(final String filename, final ReadableByteChannel channel,
final ByteBuffer buffer, final boolean filled) throws IOException
{
super(filename, channel, buffer, filled);
}
/**
* Creates a new input stream from the given {@code ChannelDataInput}.
* This constructor is invoked when we need to change the implementation class
* from {@code ChannelDataInput} to {@code ChannelImageInputStream}.
*
* @param input the existing instance from which to takes the channel and buffer.
* @throws IOException if an error occurred while reading the channel.
*/
public ChannelImageInputStream(final ChannelDataInput input) throws IOException {
super(input.filename, input.channel, input.buffer, true);
}
/**
* Sets the desired byte order for future reads of data values from this stream.
* The default value is {@link ByteOrder#BIG_ENDIAN}.
*
* @param byteOrder the new {@linkplain #buffer buffer} byte order.
*/
@Override
public final void setByteOrder(final ByteOrder byteOrder) {
buffer.order(byteOrder);
}
/**
* Returns the byte order with which data values will be read from this stream.
* This is the {@linkplain #buffer buffer} byte order.
*
* @return the {@linkplain #buffer buffer} byte order.
*/
@Override
public final ByteOrder getByteOrder() {
return buffer.order();
}
/**
* Returns the length of the stream (in bytes), or -1 if unknown.
*
* @return the length of the stream (in bytes), or -1 if unknown.
* @throws IOException if an error occurred while fetching the stream length.
*/
@Override
public final long length() throws IOException {
if (channel instanceof SeekableByteChannel) {
return ((SeekableByteChannel) channel).size();
}
return -1;
}
/**
* Reads a byte from the stream and returns a {@code true} if it is nonzero, {@code false} otherwise.
* The implementation is as below:
*
* {@preformat java
* return readByte() != 0;
* }
*
* @return the value of the next boolean from the stream.
* @throws IOException if an error (including EOF) occurred while reading the stream.
*/
@Override
public final boolean readBoolean() throws IOException {
return readByte() != 0;
}
/**
* Reads in a string that has been encoded using a UTF-8 string.
*
* @return the string reads from the stream.
* @throws IOException if an error (including EOF) occurred while reading the stream.
*/
@Override
public final String readUTF() throws IOException {
final ByteOrder oldOrder = buffer.order();
buffer.order(ByteOrder.BIG_ENDIAN);
try {
return DataInputStream.readUTF(this);
} finally {
buffer.order(oldOrder);
}
}
/**
* Reads the new bytes until the next EOL. This method can read only US-ASCII strings.
* This method is provided for compliance with the {@link java.io.DataInput} interface,
* but is generally not recommended.
*
* @return the next line, or {@code null} if the EOF has been reached.
* @throws IOException if an error occurred while reading.
*/
@Override
public final String readLine() throws IOException {
int c = read();
if (c < 0) {
return null;
}
StringBuilder line = new StringBuilder();
line.append((char) c);
loop: while ((c = read()) >= 0) {
switch (c) {
case '\r': {
c = read();
if (c >= 0 && c != '\n') {
pushBack();
}
break loop;
}
case '\n': {
break loop;
}
}
line.append((char) c);
}
return line.toString();
}
/**
* Returns the next byte from the stream as an unsigned integer between 0 and 255,
* or -1 if we reached the end of stream.
*
* @return the next byte as an unsigned integer, or -1 on end of stream.
* @throws IOException if an error occurred while reading the stream.
*/
@Override
public final int read() throws IOException {
return hasRemaining() ? Byte.toUnsignedInt(buffer.get()) : -1;
}
/**
* Reads up to {@code dest.length} bytes from the stream, and stores them into
* {@code dest} starting at index 0. The default implementation is as below:
*
* {@preformat java
* return read(dest, 0, dest.length);
* }
*
* @param dest an array of bytes to be written to.
* @return the number of bytes actually read, or -1 on EOF.
* @throws IOException if an error occurred while reading.
*/
@Override
public final int read(final byte[] dest) throws IOException {
return read(dest, 0, dest.length);
}
/**
* Reads up to {@code length} bytes from the stream, and stores them into {@code dest}
* starting at index {@code offset}. If no bytes can be read because the end of the stream
* has been reached, -1 is returned.
*
* @param dest an array of bytes to be written to.
* @param offset the starting position within {@code dest} to write.
* @param length the maximum number of bytes to read.
* @return the number of bytes actually read, or -1 on EOF.
* @throws IOException if an error occurred while reading.
*/
@Override
public final int read(final byte[] dest, int offset, int length) throws IOException {
if (!hasRemaining()) {
return -1;
}
final int requested = length;
while (length != 0 && hasRemaining()) {
final int n = Math.min(buffer.remaining(), length);
buffer.get(dest, offset, n);
offset += n;
length -= n;
}
return requested - length;
}
/**
* Reads up to {@code length} bytes from the stream, and modifies the supplied
* {@code IIOByteBuffer} to indicate the byte array, offset, and length where
* the data may be found.
*
* @param dest the buffer to be written to.
* @param length the maximum number of bytes to read.
* @throws IOException if an error occurred while reading.
*/
@Override
public final void readBytes(final IIOByteBuffer dest, int length) throws IOException {
final byte[] data = new byte[length];
length = read(data);
dest.setData(data);
dest.setOffset(0);
dest.setLength(length);
}
/**
* Skips over <var>n</var> bytes of data from the input stream.
* This implementation does not skip more bytes than the buffer capacity.
*
* @param n maximal number of bytes to skip.
* @return number of bytes actually skipped.
* @throws IOException if an error occurred while reading.
*/
@Override
public final int skipBytes(int n) throws IOException {
if (!hasRemaining()) {
return 0;
}
int r = buffer.remaining();
if (n >= r) {
n = r;
}
buffer.position(buffer.position() + n);
return n;
}
/**
* Advances the current stream position by the given amount of bytes.
* The bit offset is reset to 0 by this method.
*
* @param n the number of bytes to seek forward.
* @return the number of bytes skipped.
* @throws IOException if an error occurred while skipping.
*/
@Override
public final long skipBytes(final long n) throws IOException {
return skipBytes((int) Math.min(n, Integer.MAX_VALUE));
}
/**
* Discards the initial position of the stream prior to the current stream position.
* The implementation is as below:
*
* {@preformat java
* flushBefore(getStreamPosition());
* }
*
* @throws IOException if an I/O error occurred.
*/
@Override
public final void flush() throws IOException {
flushBefore(getStreamPosition());
}
/**
* Synonymous of {@link #isCachedMemory()} since the caching behavior of this class is uniquely determined
* by the policy that we choose for {@code isCachedMemory()}. This class never creates temporary files.
*
* @see #isCachedMemory()
* @see #isCachedFile()
*/
@Override
public final boolean isCached() {
return isCachedMemory();
}
/**
* Returns {@code false} since this {@code ImageInputStream} does not cache data itself in order to
* allow {@linkplain #seek(long) seeking} backwards. Actually, we could consider the {@link #buffer}
* as a cache in main memory. But this buffer has a maximal capacity, which would be a violation of
* {@code ImageInputStream} contract.
*
* @return {@code false} since this {@code ImageInputStream} does not caches data in main memory
* (ignoring the {@link #buffer}).
*/
@Override
public final boolean isCachedMemory() {
return false;
}
/**
* Returns {@code false} since this {@code ImageInputStream} does not cache data in a temporary file.
*
* @return {@code false} since this {@code ImageInputStream} does not cache data in a temporary file.
*/
@Override
public final boolean isCachedFile() {
return false;
}
/**
* Closes the {@linkplain #channel}.
* If the channel is backed by an {@link java.io.InputStream}, that stream will be closed too.
*
* <h4>Departure from Image I/O standard implementation</h4>
* Java Image I/O wrappers around input/output streams do not close the underlying stream (see for example
* {@link javax.imageio.stream.FileCacheImageInputStream#close()} specification). But Apache SIS needs the
* underlying stream to be closed because we do not keep reference to the original input stream. Note that
* channels created by {@link java.nio.channels.Channels#newChannel(java.io.InputStream)} close the stream,
* which is the desired behavior for this method.
*
* @throws IOException if an error occurred while closing the channel.
*/
@Override
public final void close() throws IOException {
channel.close();
}
}