/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2011-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2011-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.OutputStream; import javax.imageio.spi.ImageWriterSpi; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.geotoolkit.internal.io.TemporaryFile; import org.geotoolkit.nio.IOUtilities; import org.geotoolkit.resources.Errors; /** * Base class for image writers that require {@link File} output destination. This class is used with * image formats backed by some external API (typically C/C++ libraries) working only with files. * <p> * The output type can be any of the types documented in the * {@linkplain org.geotoolkit.image.io.StreamImageWriter.Spi provider} javadoc. The {@link File} * object can be obtained by a call to {@link #getOutputPath()}, which handles the various output * types as below: * <p> * <ul> * <li>{@link File} outputs are returned as-is.</li> * <li>{@link String} outputs are converted to {@code File} objects by a call to the * {@link File#File(String)} constructor.</li> * <li>{@link Path} outputs 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, a temporary file is returned. When the {@link #close()} method is * invoked, the content of the temporary file is copied to the output stream and the file * is deleted.</li> * </ul> * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) * @version 3.20 * * @since 3.20 * @module */ public abstract class FileImageWriter extends StreamImageWriter { /** * The file to write. This is the same reference than {@link #output} if the later was * already a {@link File} object. Otherwise this is a {@code File} derived from the * output URL, URI or Path if possible, or a temporary file otherwise. */ private Path outputPath; /** * {@code true} if {@link #outputPath} is a temporary file. */ private boolean isTemporary; /** * Constructs a new image writer. * * @param provider The {@link ImageWriterSpi} that is constructing this object, or {@code null}. */ protected FileImageWriter(final Spi provider) { super(provider); } /** * Returns the encoding used for {@linkplain URL} {@linkplain #output output}s. * The default implementation returns {@code "UTF-8"} in all cases. Subclasses * can override this method if {@link #getOutputPath()} should convert {@link URL} * to {@link File} objects using a different encoding. * * @return The encoding used for URL outputs. */ public String getURLEncoding() { return "UTF-8"; } /** * Returns the {@linkplain #output output} as a file. If the output is not a file, * then a temporary file is returned. The content of the file will be send to the * original output when {@linkplain #close() closing} this image writer. * * @return The {@linkplain #output output} as a file. * @throws IOException If the output can not be converted to a file, or a failure * occurred while creating a temporary file. */ protected Path getOutputPath() throws IOException { if (outputPath != null) { return outputPath; } final Object output = this.output; if (output == null) { throw new IllegalStateException(getErrorResources().getString(Errors.Keys.NoImageOutput)); } if (output instanceof String) { return Paths.get((String) output); } if (output instanceof File) { return ((File) output).toPath(); } if (output instanceof Path) { return (Path) output; } if (output instanceof URI) { return Paths.get((URI) output); } if (output instanceof URL) { try { final URL sourceURL = (URL) output; outputPath = Paths.get(sourceURL.toURI()); return outputPath; } catch (URISyntaxException e) { //forward exception throw new IOException(e); } } /* * Can not convert the output directly to a file. Creates a temporary file using * the first declared image suffix (e.g. "png"), or "tmp" if there is no declared * suffix. The "FIW" prefix stands for "FileImageWriter". */ outputPath = TemporaryFile.createTempFile("FIW", XImageIO.getFileSuffix(originatingProvider), null); isTemporary = true; return outputPath; } /** * Returns {@code true} if the file given by {@link #getOutputPath()} is a temporary file. * * @return {@code true} if the output file is a temporary one. */ protected boolean isTemporaryFile() { return isTemporary; } /** * Flushes the image content to the output stream, closes the stream and deletes the * temporary file (if any). More specifically, this method performs the following steps: * <p> * <ol> * <li>If the content was written to a temporary file, copy that content to the original * {@linkplain #getOutputStream() output stream}.</li> * <li>Deletes the temporary file (if any).</li> * <li>Closes the output stream {@linkplain StreamImageWriter#close() as documented in * the super-class}</li> * </ol> * <p> * This method is invoked automatically by {@link #setOutput(Object)}, {@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 occurred while disposing resources. */ @Override protected void close() throws IOException { final Path path = outputPath; outputPath = null; if (isTemporary) try { isTemporary = false; final OutputStream out = getOutputStream(); try (InputStream in = Files.newInputStream(path)) { IOUtilities.copy(in, out); } out.flush(); // Do not close the 'out' stream. Let the super.close() method decides what // it needs to close (it depends if the stream was specified by the user or // created under the hood by the writer). } finally { // Delete the temporary path before to close the stream in order to make sure that // it is deleted even if super.close() failed. In extreme cases, this may also free // some disk space needed by super.close() for completing its work. if (!TemporaryFile.delete(path.toFile())) { path.toFile().deleteOnExit(); } super.close(); } else { super.close(); } } }