/*
* 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.WritableByteChannel;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
import org.geotoolkit.resources.Errors;
/**
* Base class for image writers that expect an {@link OutputStream} or channel output. This class
* provides a {@link #getOutputStream()} method, which return the {@linkplain #output output} as
* an {@link OutputStream} 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 OutputStream}, {@link ImageOutputStream}, {@link WritableByteChannel}.
* </blockquote>
*
* Note the {@link TextImageWriter} subclass can go one step further by wrapping the
* {@code InputStream} into a {@link java.io.BufferedWriter} using some character encoding.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.20
*
* @see StreamImageReader
*
* @since 2.4
* @module
*/
public abstract class StreamImageWriter extends SpatialImageWriter {
/**
* The stream to {@linkplain #close() close} on {@link #setOutput(.Object)}, {@link #reset()}
* or {@link #dispose()} method invocation. This stream is typically an
* {@linkplain OutputStream output stream} or a {@linkplain Writer writer}
* created by {@link #getOutputStream()} or similar methods in subclasses.
* <p>
* This field is never equal to the user-specified {@linkplain #output output}, since the
* usual {@link javax.imageio.ImageWriter} 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 #getOutputStream()
* @see TextImageWriter#getWriter(ImageWriteParam)
* @see #close()
*/
protected Closeable closeOnReset;
/**
* {@link #output} as an output stream, or {@code null} if none.
*
* @see #getOutputStream()
*/
private OutputStream stream;
/**
* Constructs a new image writer.
*
* @param provider The {@link ImageWriterSpi} that is constructing this object, or {@code null}.
*/
protected StreamImageWriter(final Spi provider) {
super(provider);
}
/**
* Sets the output source to use. Output can be any 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 OutputStream}, {@link ImageOutputStream}
* or {@link WritableByteChannel}.
* <p>
* If the given {@code output} is {@code null}, then any currently set output source will be
* removed.
*
* @param output The output object to use for future writing.
*
* @see #getOutput
* @see #getOutputStream
*/
@Override
public void setOutput(final Object output) {
super.setOutput(output);
}
/**
* Returns the {@linkplain #output output} as an {@linkplain OutputStream output stream} object.
* If the output is already an output stream, it is returned unchanged. Otherwise this method
* creates a new {@linkplain OutputStream output stream} (usually <strong>not</strong>
* {@linkplain BufferedOutputStream buffered}) from {@link File}, {@link Path}, {@link URI},
* {@link URL}, {@link URLConnection}, {@link ImageOutputStream} or {@link WritableByteChannel}
* outputs.
* <p>
* This method creates a new {@linkplain OutputStream output 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 #setOutput}, {@link #reset()} or {@link #dispose()} methods are invoked.
*
* @return {@link #getOutput} as an {@link OutputStream}. This output stream is usually
* not {@linkplain BufferedOutputStream buffered}.
* @throws IllegalStateException if the {@linkplain #output output} is not set.
* @throws IOException If the output stream can't be created for an other reason.
*
* @see #getOutput
* @see TextImageWriter#getWriter(ImageWriteParam)
*/
protected OutputStream getOutputStream() throws IllegalStateException, IOException {
if (stream == null) {
final Object output = getOutput();
if (output == null) {
throw new IllegalStateException(getErrorResources().getString(Errors.Keys.NoImageOutput));
}
if (output instanceof OutputStream) {
stream = (OutputStream) output;
closeOnReset = null; // We don't own the stream, so don't close it.
} else if (output instanceof ImageOutputStream) {
stream = new OutputStreamAdapter((ImageOutputStream) output);
closeOnReset = null; // We don't own the ImageOutputStream, so don't close it.
} else if (output instanceof String) {
stream = new FileOutputStream((String) output);
closeOnReset = stream;
} else if (output instanceof File) {
stream = new FileOutputStream((File) output);
closeOnReset = stream;
} else if (output instanceof Path) {
stream = Files.newOutputStream((Path) output);
closeOnReset = stream;
} else if (output instanceof URI) {
stream = ((URI) output).toURL().openConnection().getOutputStream();
closeOnReset = stream;
} else if (output instanceof URL) {
stream = ((URL) output).openConnection().getOutputStream();
closeOnReset = stream;
} else if (output instanceof URLConnection) {
stream = ((URLConnection) output).getOutputStream();
closeOnReset = stream;
} else if (output instanceof WritableByteChannel) {
stream = Channels.newOutputStream((WritableByteChannel) output);
// Do not define closeOnReset since we don't want to close user-provided output.
} else {
throw new IllegalStateException(getErrorResources().getString(
Errors.Keys.IllegalClass_2, output.getClass(), OutputStream.class));
}
}
return stream;
}
/**
* Closes the output stream created by {@link #getOutputStream()}. This method does nothing
* if the output stream is the {@linkplain #output output} instance given by the user rather
* than a stream created by this class from a {@link File} or {@link URL} output.
* <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 closing the stream.
*
* @see #closeOnReset
*/
@Override
protected void close() throws IOException {
if (closeOnReset != null) {
closeOnReset.close();
}
closeOnReset = null;
stream = null;
super.close();
}
/**
* Invokes the {@link #close()} method when this writer is garbage-collected.
* Note that this will actually close the stream only if it has been created
* by this writer, rather than supplied by the user.
*/
@Override
protected void finalize() throws Throwable {
closeSilently();
super.finalize();
}
/**
* Service provider interface (SPI) for {@link StreamImageWriter}s. The constructor of
* this class initializes the {@link #outputTypes} field to the value documented in the
* {@link StreamImageWriter} javadoc, which are:
* <p>
* <table border="1">
* <tr bgcolor="lightblue">
* <th>Field</th>
* <th>Value</th>
* </tr><tr>
* <td> {@link #outputTypes} </td>
* <td> {@link String}, {@link Path}, {@link File}, {@link URI}, {@link URL},
* {@link URLConnection}, {@link OutputStream}, {@link ImageOutputStream},
* {@link WritableByteChannel} </td>
* </tr><tr>
* <td colspan="2" align="center">See {@linkplain SpatialImageWriter.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)
* @version 3.20
*
* @see StreamImageReader.Spi
*
* @since 2.4
* @module
*/
protected abstract static class Spi extends SpatialImageWriter.Spi {
/**
* List of legal output types for {@link StreamImageWriter}.
*/
private static final Class<?>[] OUTPUT_TYPES = new Class<?>[] {
File.class,
Path.class,
URL.class,
URLConnection.class,
OutputStream.class,
ImageOutputStream.class,
String.class // To be interpreted as file path.
};
/**
* Constructs a quasi-blank {@code StreamImageWriter.Spi}. The {@link #outputTypes} 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 above fields are initialized to shared arrays. Subclasses
* can assign new arrays, but should not modify the default array content.
*/
protected Spi() {
outputTypes = OUTPUT_TYPES;
}
/**
* Returns {@code true} if the image writer implementation associated with this service
* provider is able to encode an image with the given layout. The default implementation
* returns always {@code true}, which is accurate if the writer will fetch pixel values
* with the help of an {@linkplain SpatialImageWriter#createRectIter iterator}.
*/
@Override
public boolean canEncodeImage(final ImageTypeSpecifier type) {
return true;
}
}
}