/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2001-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * 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.geotoolkit.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 java.nio.file.Path; import java.nio.file.Files; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import org.geotoolkit.resources.Errors; import org.apache.sis.util.logging.Logging; /** * Base class for image readers that expect an {@link InputStream} or channel input source. This * class provides a {@link #getInputStream()} method, which return the {@linkplain #input input} * as an {@link InputStream} for convenience. * <p> * Different kinds of outputs are automatically handled. * The default implementation handles all the following types: * * <blockquote> * {@link String}, {@link Path}, {@link File}, {@link URI}, {@link URL}, {@link URLConnection}, * {@link InputStream}, {@link ImageInputStream}, {@link ReadableByteChannel}. * </blockquote> * * Note the {@link TextImageReader} subclass can go one step further by wrapping the * {@code InputStream} into a {@link java.io.BufferedReader} using some character encoding. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.20 * * @see StreamImageWriter * * @since 2.4 * @module */ public abstract class StreamImageReader extends SpatialImageReader { /** * 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 equal 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 TextImageReader#getReader() * @see #close() */ protected Closeable closeOnReset; /** * The channel returned by {@link #getChannel()}, or {@code null} if none. */ private ReadableByteChannel channel; /** * {@link #input} as an input stream, or {@code null} if none. * * @see #getInputStream() */ private InputStream stream; /** * The stream position when {@link #setInput(Object, boolean, boolean)} has been 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 Spi provider) { super(provider); } /** * Sets the input source to use. Input may be one of the object documented in the * <a href="#overview">class javadoc</a>, namely {@link String}, {@link Path}, {@link File}, * {@link URI}, {@link URL}, {@link URLConnection}, {@link InputStream}, {@link ImageInputStream} * or {@link ReadableByteChannel}. * <p> * If the given {@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) { super.setInput(input, seekForwardOnly, ignoreMetadata); if (input instanceof ImageInputStream) { try { streamOrigin = ((ImageInputStream) input).getStreamPosition(); } catch (IOException exception) { 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()}, {@link URLConnection#getContentLength()} or * {@link Files#size(Path)} method accordingly. * * @return The stream length, or -1 is unknown. * @throws IOException if an I/O error occurred. */ 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 Path) { return Files.size((Path) input); } 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 Path}, {@link URI}, * {@link URL}, {@link URLConnection}, {@link ImageInputStream} or {@link ReadableByteChannel} * inputs. * <p> * This method creates a new {@linkplain InputStream input stream} only when first invoked. * All subsequent calls will return the same instance. Consequently, the returned stream * should never be closed by the caller. It will be {@linkplain #close() closed} automatically * (if the stream was not given directly by the user) when {@link #setInput setInput(...)}, * {@link #reset() reset()} or {@link #dispose() 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 #getChannel() * @see TextImageReader#getReader() */ protected InputStream getInputStream() throws IllegalStateException, IOException { if (stream == null) { final Object input = getInput(); if (input == null) { throw new IllegalStateException(getErrorResources().getString(Errors.Keys.NoImageInput)); } 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 Path) { stream = Files.newInputStream((Path) 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 if (input instanceof ReadableByteChannel) { stream = Channels.newInputStream((ReadableByteChannel) input); // Do not define closeOnReset since we don't want to close user-provided input. } else { throw new IllegalStateException(getErrorResources().getString( Errors.Keys.IllegalClass_2, input.getClass(), InputStream.class)); } } return stream; } /** * Returns the {@linkplain #input input} as an {@linkplain ReadableByteChannel readable byte * channel}. If the input is already such channel, it is returned unchanged. Otherwise this * method creates a new channel from the {@link Path} input or the value returned by * {@link #getInputStream()}. * <p> * This method creates a new channel only when first invoked. All subsequent calls will return * the same instance. Consequently, the returned channel should never be closed by the caller. * It will be {@linkplain #close() closed} automatically (if the channel was not given directly * by the user) when {@link #setInput setInput(...)}, {@link #reset() reset()} or * {@link #dispose() dispose()} methods are invoked. * * @return {@link #getInput()} as a {@link ReadableByteChannel}. * @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 #getInputStream() * * @since 3.07 */ protected ReadableByteChannel getChannel() throws IllegalStateException, IOException { if (channel == null) { final Object input = getInput(); if (input instanceof ReadableByteChannel) { channel = (ReadableByteChannel) input; } else if (input instanceof Path) { channel = Files.newByteChannel((Path) input); } else { final InputStream stream = getInputStream(); if (stream instanceof FileInputStream) { channel = ((FileInputStream) stream).getChannel(); } else { channel = Channels.newChannel(stream); } } } return channel; } /** * 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() reset()}, {@link #dispose() 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. * Overriding methods shall make sure that {@code super.close()} is invoked * even if their own code fail. * * @throws IOException if an error occurred while closing the stream. * * @see #closeOnReset */ @Override protected void close() throws IOException { super.close(); // Should be first. if (closeOnReset != null) { // Close the channel only if it was not supplied explicitly by the user. if (channel != null) { channel.close(); } closeOnReset.close(); closeOnReset = null; } channel = null; stream = null; } /** * Invokes the {@link #close()} method when this reader is garbage-collected. * Note that this will actually close the stream only if it has been created * by this reader, rather than supplied by the user. */ @Override protected void finalize() throws Throwable { closeSilently(); super.finalize(); } /** * Service provider interface (SPI) for {@link StreamImageReader}s. The constructor of * this class initializes the {@link #inputTypes} field to the value documented in the * {@link StreamImageReader} javadoc, which are: * <p> * <table border="1"> * <tr bgcolor="lightblue"> * <th>Field</th> * <th>Value</th> * </tr><tr> * <td> {@link #inputTypes} </td> * <td> {@link String}, {@link Path}, {@link File}, {@link URI}, {@link URL}, * {@link URLConnection}, {@link InputStream}, {@link ImageInputStream}, * {@link ReadableByteChannel} </td> * </tr><tr> * <td colspan="2" align="center">See {@linkplain SpatialImageReader.Spi super-class javadoc} * for remaining fields</td> * </tr> * </table> * <p> * It is up to subclass constructors to initialize all other instance variables * in order to provide working versions of every methods. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.20 * * @see StreamImageWriter.Spi * * @since 2.4 * @module */ protected abstract static class Spi extends SpatialImageReader.Spi { /** * List of legal input types for {@link StreamImageReader}. */ private static final Class<?>[] INPUT_TYPES = new Class<?>[] { File.class, Path.class, URI.class, URL.class, URLConnection.class, InputStream.class, ImageInputStream.class, ReadableByteChannel.class, String.class // To be interpreted as file path. }; /** * Constructs a quasi-blank {@code StreamImageReader.Spi}. The {@link #inputTypes} field * is initialized as documented in the <a href="#skip-navbar_top">class javadoc</a>. It is * up to the subclass to initialize all other instance variables in order to provide working * versions of all methods. * <p> * For efficiency reasons, the {@code inputTypes} field is initialized to a shared array. * Subclasses can assign new arrays, but should not modify the default array content. */ protected Spi() { inputTypes = INPUT_TYPES; } } }