/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.image.io;
import java.io.*; // Many imports, including some for javadoc only.
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.geotools.util.logging.Logging;
import org.geotools.resources.Classes;
import org.geotools.resources.i18n.ErrorKeys;
/**
* Base class for simple image decoders. This class provides a {@link #getInputStream} method,
* which returns the {@linkplain #input input} as an {@link InputStream} for convenience.
* Different kinds of input like {@linkplain File} or {@linkplain URL} are automatically
* handled.
*
* @since 2.4
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public abstract class StreamImageReader extends GeographicImageReader {
/**
* The stream to {@linkplain #close close} on {@link #setInput(Object,boolean,boolean)
* setInput(...)}, {@link #reset} or {@link #dispose} method invocation. This stream is
* typically an {@linkplain InputStream input stream} or a {@linkplain Reader reader}
* created by {@link #getInputStream} or similar methods in subclasses.
* <p>
* This field is never equals to the user-specified {@linkplain #input input}, since the
* usual {@link javax.imageio.ImageReader} contract is to <strong>not</strong> close the
* user-provided stream. It is set to a non-null value only if a stream has been created
* from an other user object like {@link File} or {@link URL}.
*
* @see #getInputStream
* @see org.geotools.image.io.text.TextImageReader#getReader
* @see #close
*/
protected Closeable closeOnReset;
/**
* {@link #input} as an input stream, or {@code null} if none.
*
* @see #getInputStream
*/
private InputStream stream;
/**
* The stream position when {@link #setInput} is invoked.
*/
private long streamOrigin;
/**
* Constructs a new image reader.
*
* @param provider The {@link ImageReaderSpi} that is invoking this constructor,
* or {@code null} if none.
*/
protected StreamImageReader(final ImageReaderSpi provider) {
super(provider);
}
/**
* Sets the input source to use. Input may be one of the following object:
* {@link File}, {@link URL}, {@link Reader} (for ASCII data), {@link InputStream} or
* {@link ImageInputStream}. If {@code input} is {@code null}, then any currently
* set input source will be removed.
*
* @param input The input object to use for future decoding.
* @param seekForwardOnly If {@code true}, images and metadata may only be read
* in ascending order from this input source.
* @param ignoreMetadata If {@code true}, metadata may be ignored during reads.
*
* @see #getInput
* @see #getInputStream
*/
@Override
public void setInput(final Object input,
final boolean seekForwardOnly,
final boolean ignoreMetadata)
{
closeSilently();
super.setInput(input, seekForwardOnly, ignoreMetadata);
if (input instanceof ImageInputStream) {
try {
streamOrigin = ((ImageInputStream) input).getStreamPosition();
} catch (IOException exception) {
streamOrigin = 0;
Logging.unexpectedException(LOGGER, StreamImageReader.class, "setInput", exception);
}
}
}
/**
* Returns the stream length in bytes, or {@code -1} if unknown. This method checks the
* {@linkplain #input input} type and invokes one of {@link File#length()},
* {@link ImageInputStream#length()} ou {@link URLConnection#getContentLength()} method
* accordingly.
*
* @return The stream length, or -1 is unknown.
* @throws IOException if an I/O error occured.
*/
protected long getStreamLength() throws IOException {
final Object input = getInput();
if (input instanceof ImageInputStream) {
long length = ((ImageInputStream) input).length();
if (length >= 0) {
length -= streamOrigin;
}
return length;
}
if (input instanceof File) {
return ((File) input).length();
}
if (input instanceof URL) {
return ((URL) input).openConnection().getContentLength();
}
if (input instanceof URLConnection) {
return ((URLConnection) input).getContentLength();
}
return -1;
}
/**
* Returns the {@linkplain #input input} as an {@linkplain InputStream input stream} object.
* If the input is already an input stream, it is returned unchanged. Otherwise this method
* creates a new {@linkplain InputStream input stream} (usually <strong>not</strong>
* {@linkplain BufferedInputStream buffered}) from {@link File}, {@link URL},
* {@link URLConnection} or {@link ImageInputStream} inputs.
* <p>
* This method creates a new {@linkplain InputStream input stream} only when first invoked.
* All subsequent calls will returns the same instance. Consequently, the returned stream
* should never be closed by the caller. It may be {@linkplain #close closed} automatically
* when {@link #setInput setInput(...)}, {@link #reset()} or {@link #dispose()} methods are
* invoked.
*
* @return {@link #getInput} as an {@link InputStream}. This input stream is usually
* not {@linkplain BufferedInputStream buffered}.
* @throws IllegalStateException if the {@linkplain #input input} is not set.
* @throws IOException If the input stream can't be created for an other reason.
*
* @see #getInput
* @see org.geotools.image.io.text.TextImageReader#getReader
*/
protected InputStream getInputStream() throws IllegalStateException, IOException {
if (stream == null) {
final Object input = getInput();
if (input == null) {
throw new IllegalStateException(getErrorResources().getString(ErrorKeys.NO_IMAGE_INPUT));
}
if (input instanceof InputStream) {
stream = (InputStream) input;
closeOnReset = null; // We don't own the stream, so don't close it.
} else if (input instanceof ImageInputStream) {
stream = new InputStreamAdapter((ImageInputStream) input);
closeOnReset = null; // We don't own the ImageInputStream, so don't close it.
} else if (input instanceof String) {
stream = new FileInputStream((String) input);
closeOnReset = stream;
} else if (input instanceof File) {
stream = new FileInputStream((File) input);
closeOnReset = stream;
} else if (input instanceof URI) {
stream = ((URI) input).toURL().openStream();
closeOnReset = stream;
} else if (input instanceof URL) {
stream = ((URL) input).openStream();
closeOnReset = stream;
} else if (input instanceof URLConnection) {
stream = ((URLConnection) input).getInputStream();
closeOnReset = stream;
} else {
throw new IllegalStateException(getErrorResources().getString(
ErrorKeys.ILLEGAL_CLASS_$2, Classes.getClass(input), InputStream.class));
}
}
return stream;
}
/**
* Closes the input stream created by {@link #getInputStream()}. This method does nothing
* if the input stream is the {@linkplain #input input} instance given by the user rather
* than a stream created by this class from a {@link File} or {@link URL} input.
* <p>
* This method is invoked automatically by {@link #setInput(Object,boolean,boolean)
* setInput(...)}, {@link #reset}, {@link #dispose} or {@link #finalize} methods and
* doesn't need to be invoked explicitly. It has protected access only in order to
* allow overriding by subclasses.
*
* @throws IOException if an error occured while closing the stream.
*
* @see #closeOnReset
*/
@Override
protected void close() throws IOException {
if (closeOnReset != null) {
closeOnReset.close();
}
closeOnReset = null;
stream = null;
super.close();
}
/**
* Invokes {@link #close} and log the exception if any. This method is invoked from
* methods that do not allow {@link IOException} to be thrown. Since we will not use
* the stream anymore after closing it, it should not be a big deal if an error occured.
*/
private void closeSilently() {
try {
close();
} catch (IOException exception) {
Logging.unexpectedException(LOGGER, getClass(), "close", exception);
}
}
/**
* Restores the {@code StreamImageReader} to its initial state. If an input stream were
* created by a previous call to {@link #getInputStream}, it will be {@linkplain #close
* closed} before to reset this reader.
*/
@Override
public void reset() {
closeSilently();
super.reset();
}
/**
* Allows any resources held by this reader to be released. If an input stream were created
* by a previous call to {@link #getInputStream}, it will be {@linkplain #close closed}
* before to dispose this reader.
*/
@Override
public void dispose() {
closeSilently();
super.dispose();
}
/**
* Closes the streams. This method is automatically invoked by the garbage collector.
*/
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
/**
* Service provider interface (SPI) for {@link StreamImageReader}s.
*
* @since 2.4
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public static abstract class Spi extends ImageReaderSpi {
/**
* List of legal input types for {@link StreamImageReader}.
*/
private static final Class[] INPUT_TYPES = new Class[] {
File.class,
URI.class,
URL.class,
URLConnection.class,
InputStream.class,
ImageInputStream.class,
String.class // To be interpreted as file path.
};
/**
* Constructs a quasi-blank {@code StreamImageReader.Spi}. It is up to the subclass to
* initialize instance variables in order to provide working versions of all methods.
* This constructor provides the following defaults:
*
* <ul>
* <li>{@link #inputTypes} = {{@link File}, {@link URL}, {@link URLConnection},
* {@link InputStream}, {@link ImageInputStream}, {@link String}}</li>
* </ul>
*
* For efficienty reasons, the above fields are initialized to shared arrays. Subclasses
* can assign new arrays, but should not modify the default array content.
*/
public Spi() {
inputTypes = INPUT_TYPES;
}
}
}