/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2006-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.net.URISyntaxException; import java.net.URL; import java.net.URI; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.FileNotFoundException; import javax.imageio.spi.ImageReaderSpi; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.geotoolkit.resources.Errors; import org.geotoolkit.internal.io.TemporaryFile; /** * Base class for image readers that require {@link File} input source. This class is used with * image formats backed by some external API (typically C/C++ libraries) working only with files. * <p> * The input type can be any of the types documented in the * {@linkplain org.geotoolkit.image.io.StreamImageReader.Spi provider} javadoc. The {@link File} * object can be obtained by a call to {@link #getInputPath()}, which handles the various input * types as below: * <p> * <ul> * <li>{@link File} inputs are returned as-is.</li> * <li>{@link String} inputs are converted to {@code File} objects by a call to the * {@link File#File(String)} constructor.</li> * <li>{@link Path} inputs are converted to {@code File} objects by a call to * {@link Path#toFile()} method.</li> * <li>{@link URL} and {@link URI} inputs are converted to {@code File} objects by a call to the * {@link File#File(URI)} constructor only if the protocol is {@code "file"}. In the particular * case of {@code URL}s, the encoding is specified by {@link #getURLEncoding()}.</li> * <li>For all other cases, the input content is copied to a temporary file and the corresponding * {@code File} object is returned. The temporary file is deleted by the {@link #close()} * method.</li> * </ul> * * @author Antoine Hnawia (IRD) * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.20 * * @since 2.4 * @module */ public abstract class FileImageReader extends StreamImageReader { /** * The file to read. This is the same reference than {@link #input} if the later was * already a {@link File} object. Otherwise this is a {@code File} derived from the * input URL, URI or Path if possible, or a temporary file otherwise. */ private Path inputPath; /** * {@code true} if {@link #inputPath} is a temporary file. */ private boolean isTemporary; /** * Constructs a new image reader. * * @param provider The {@link ImageReaderSpi} that is constructing this object, or {@code null}. */ protected FileImageReader(final Spi provider) { super(provider); } /** * Returns the encoding used for {@linkplain URL} {@linkplain #input input}s. * The default implementation returns {@code "UTF-8"} in all cases. Subclasses * can override this method if {@link #getInputPath()} should convert {@link URL} * to {@link File} objects using a different encoding. * * @return The encoding used for URL inputs. */ public String getURLEncoding() { return "UTF-8"; } /** * Ensures that the specified file can be read. * * @throws FileNotFoundException if the file is not found or can not be read. */ private void ensureFileExists(final Path path) throws FileNotFoundException { if (!Files.isRegularFile(path) || !Files.isReadable(path)) { throw new FileNotFoundException(getErrorResources().getString( Errors.Keys.FileDoesNotExist_1, path)); } } /** * Returns the {@linkplain #input input} as a file. If the input is not a file, * then its content is copied to a temporary file and the temporary file is returned. * * @return The {@linkplain #input input} as a file. * @throws FileNotFoundException if the file is not found or can not be read. * @throws IOException if a copy was necessary but failed. */ protected Path getInputPath() throws FileNotFoundException, IOException { if (inputPath != null) { ensureFileExists(inputPath); return inputPath; } if (input instanceof String) { inputPath = Paths.get((String) input); ensureFileExists(inputPath); return inputPath; } if (input instanceof File) { inputPath = ((File) input).toPath(); ensureFileExists(inputPath); return inputPath; } if (input instanceof Path) { inputPath = (Path) input; ensureFileExists(inputPath); return inputPath; } if (input instanceof URI) { inputPath = Paths.get((URI) input); ensureFileExists(inputPath); return inputPath; } if (input instanceof URL) { try { final URL sourceURL = (URL) input; inputPath = Paths.get(sourceURL.toURI()); ensureFileExists(inputPath); return inputPath; } catch (URISyntaxException e) { //forward exception throw new IOException(e); } } /* * Can not convert the input directly to a path. Asks the input stream * before to create the temporary path in case an exception is thrown. * Then creates a temporary file using the first declared image suffix * (e.g. "png"), or "tmp" if there is no declared suffix. The "FIR" * prefix stands for "FileImageReader". */ final InputStream in = getInputStream(); inputPath = TemporaryFile.createTempFile("FIR", XImageIO.getFileSuffix(originatingProvider), null); isTemporary = true; Files.copy(in, inputPath); /* * Do not close the input stream, because it may be a stream explicitly specified by the user. * The stream will be closed by the 'setInput', 'reset', 'close' or 'dispose' methods. */ return inputPath; } /** * Returns {@code true} if the path given by {@link #getInputPath()} is a temporary file. * * @return {@code true} if the input path is a temporary one. */ protected boolean isTemporaryFile() { return isTemporary; } /** * Returns {@code true} since image readers backed by {@link File} * object usually supports random access efficiently. * * @throws IOException If an error occurred while fetching the information. */ @Override public boolean isRandomAccessEasy(final int imageIndex) throws IOException { return true; } /** * Closes the stream {@linkplain StreamImageReader#close() as documented in the super-class}, * then deletes the temporary file (if any). 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. * * @throws IOException If an error occurred while disposing resources. */ @Override protected void close() throws IOException { try { super.close(); // Must close the stream before to delete the file. } finally { final Path path = inputPath; if (path != null) { inputPath = null; if (isTemporary) { if (!TemporaryFile.delete(path.toFile())) { inputPath.toFile().deleteOnExit(); } } } isTemporary = false; } } }