/*
 * $RCSfile: ImageWriteDescriptor.java,v $
 *
 * 
 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 * 
 * - Redistribution of source code must retain the above copyright 
 *   notice, this  list of conditions and the following disclaimer.
 * 
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in 
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of 
 * contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any 
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES. 
 * 
 * You acknowledge that this software is not designed or intended for 
 * use in the design, construction, operation or maintenance of any 
 * nuclear facility. 
 *
 * $Revision: 1.1 $
 * $Date: 2005-02-11 05:01:56 $
 * $State: Exp $
 */
package com.sun.media.jai.operator;

import java.awt.Dimension;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderableImage;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.util.Collection;
import java.util.EventListener;
import java.util.Iterator;
import java.util.Locale;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.CollectionOp;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptorImpl;
import javax.media.jai.RenderableOp;
import javax.media.jai.RenderedOp;
import javax.media.jai.registry.CollectionRegistryMode;
import javax.media.jai.registry.RenderableRegistryMode;
import javax.media.jai.registry.RenderedRegistryMode;

/**
 * An <code>OperationDescriptor</code> describing the "ImageWrite" operation.
 *
 * <p>The "ImageWrite" operation uses the
 * <a href="http://java.sun.com/j2se/1.4/docs/guide/imageio/index.html">Java
 * Image I/O Framework</a> to write images to an output destination. Which
 * formats may be written depends on which {@link javax.imageio.ImageWriter}
 * plug-ins are registered with the Image I/O Framework when the operation is
 * invoked.</p>
 *
 * <p>The output destination will usually be an
 * {@link javax.imageio.stream.ImageOutputStream}, but may be a
 * {@link java.io.File}, {@link java.io.RandomAccessFile},
 * {@link java.io.OutputStream}, {@link java.net.Socket},
 * {@link java.nio.channels.WritableByteChannel}, file path represented as a
 * <code>String</code> or some other type compatible with a writer plug-in. The
 * {@link javax.imageio.ImageIO} class should be used to specify the location
 * and enable the use of cache files via its <code>setCacheDirectory()</code>
 * and <code>setUseCache()</code> methods, respectively. Note that this cache
 * pertains to image stream caching and is unrelated to the JAI
 * <code>TileCache</code>. If an {@link javax.imageio.stream.ImageOutputStream}
 * is created internally by the operation, for example from a
 * {@link java.io.File}-valued <a href="#ParamOutput">Output</a> parameter,
 * then it will be flushed automatically if and only if the operation is not
 * in <a href="#CollectionMode">collection mode</a> and pixel replacement is
 * not occurring.</p>
 *
 * <p>The "ImageWrite" operation supports <a href="#RenderedMode">rendered</a>,
 * <a href="#RenderableMode">renderable</a>, and
 * <a href="#CollectionMode">collection</a> modes and requires a single
 * source. The operation is "immediate" for all modes as specified by
 * <code>OperationDescriptor.isImmediate()</code> so that
 * {@link #isImmediate()} returns <code>true</code>. The operation will
 * therefore be rendered when created via either <code>JAI.create[NS]()</code>
 * or <code>JAI.createCollection[NS]()</code>.
 * A {@link java.awt.RenderingHints} object supplied when the
 * operation is created will have no effect except with respect to the
 * mapping of <code>JAI.KEY_INTERPOLATION</code> and then only in renderable
 * mode.</p>
 *
 * <p>Image properties are used to pass metadata and other information to the
 * writer plug-in and to make available metadata as actually written to the
 * output destination. Property handling is mode-specific.</p>
 *
 * <p><table border=1>
 * <caption><b>Resource List</b></caption>
 * <tr><th>Name</th>        <th>Value</th></tr>
 * <tr><td>GlobalName</td>  <td>ImageWrite</td></tr>
 * <tr><td>LocalName</td>   <td>ImageWrite</td></tr>
 * <tr><td>Vendor</td>      <td>com.sun.media.jai</td></tr>
 * <tr><td>Description</td> <td>Writes an image using the Java Image I/O Framework.</td></tr>
 * <tr><td>DocURL</td>      <td>http://java.sun.com/products/java-media/jai/forDevelopers/jai-imageio-1_0-rc-docs/com/sun/media/jai/operator/ImageWriteDescriptor.html</td></tr>
 * <tr><td>Version</td>     <td>1.0</td></tr>
 * <tr><td>arg0Desc</td>    <td>The output destination.</td></tr>
 * <tr><td>arg1Desc</td>    <td>The format name of the output.</td></tr>
 * <tr><td>arg2Desc</td>    <td>Whether to use image metadata properties as fallbacks.</td></tr>
 * <tr><td>arg3Desc</td>    <td>Whether to transcode metadata before writing.</td></tr>
 * <tr><td>arg4Desc</td>    <td>Whether to verify the validity of the output destination.</td></tr>
 * <tr><td>arg5Desc</td>    <td>Whether to allow pixel replacement in the output image.</td></tr>
 * <tr><td>arg6Desc</td>    <td>The tile size of the output image.</td></tr>
 * <tr><td>arg7Desc</td>    <td>Stream metadata to write to the output.</td></tr>
 * <tr><td>arg8Desc</td>    <td>Image metadata to write to the output.</td></tr>
 * <tr><td>arg9Desc</td>    <td>Thumbnails to write to the output.</td></tr>
 * <tr><td>arg10Desc</td>    <td>EventListeners to be registered with the ImageWriter.</td></tr>
 * <tr><td>arg11Desc</td>    <td>The Locale for the ImageWriter to use.</td></tr>
 * <tr><td>arg12Desc</td>    <td>Java Image I/O write parameter instance.</td></tr>
 * <tr><td>arg13Desc</td>    <td>Java Image I/O writer instance.</td></tr>
 * </table></p>
 *
 * <h2><a name="RenderedMode"</a>Rendered Mode</h2>
 *
 * In rendered mode the "ImageWrite" operation writes a
 * {@link java.awt.image.RenderedImage} to the specified output destination.
 *
 * <h3><a name="RenderedModeParameters"</a>Rendered Mode Parameters</h3>
 *
 * The parameter list of the "ImageWrite" operation in rendered mode is
 * as in the following table.
 *
 * <p><table border=1>
 * <caption><b>Rendered Mode Parameter List</b></caption>
 * <tr><th>Name</th>           <th>Class Type</th>
 *                             <th>Default Value</th></tr>
 * <tr><td><a href="#ParamOutput">
 *     Output</a></td>          <td>java.lang.Object.class</td>
 *                             <td>NO_PARAMETER_DEFAULT</td>
 * <tr><td><a href="#ParamFormat">
 *     Format</a></td>    <td>java.lang.String</td>
 *                             <td>null</td>
 * <tr><td><a href="#ParamUseProperties">
 *     UseProperties</a></td>   <td>java.lang.Boolean</td>
 *                             <td>TRUE</td>
 * <tr><td><a href="#ParamTranscode">
 *     Transcode</a></td> <td>java.lang.Boolean</td>
 *                             <td>TRUE</td>
 * <tr><td><a href="#ParamVerifyOutput">
 *     VerifyOutput</a></td>    <td>java.lang.Boolean</td>
 *                             <td>TRUE</td>
 * <tr><td><a href="#ParamAllowPixelReplacement">
 *     AllowPixelReplacement</a></td> <td>java.lang.Boolean</td>
 *                             <td>FALSE</td>
 * <tr><td><a href="#ParamTileSize">
 *     TileSize</a></td>    <td>java.awt.Dimension</td>
 *                             <td>null</td>
 * <tr><td><a href="#ParamStreamMetadata">
 *     StreamMetadata</a></td>    <td>javax.imageio.metadata.IIOMetadata</td>
 *                             <td>null</td>
 * <tr><td><a href="#ParamImageMetadata">
 *     ImageMetadata</a></td>    <td>javax.imageio.metadata.IIOMetadata</td>
 *                             <td>null</td>
 * <tr><td><a href="#ParamThumbnails">
 *     Thumbnails</a></td>    <td>java.awt.BufferedImage[]</td>
 *                             <td>null</td>
 * <tr><td><a href="#ParamListeners">
 *     Listeners</a></td>      <td>java.util.EventListener[]</td>
 *                             <td>null</td>
 * <tr><td><a href="#ParamLocale">
 *     Locale</a></td>         <td>java.util.Locale</td>
 *                             <td>null</td>
 * <tr><td><a href="#ParamWriteParam">
 *     WriteParam</a></td>      <td>javax.imageio.ImageWriteParam</td>
 *                             <td>null</td>
 * <tr><td><a href="#ParamWriter">
 *     Writer</a></td>         <td>javax.imageio.ImageWriter</td>
 *                             <td>null</td>
 * </table></p>
 *
 * <p>The rendered mode parameters are handled as follows:
 *
 * <ul>
 * <p><li><a name="ParamOutput"</a>
 * If Output is a String it is assumed to represent a file path.
 * </li></p>
 * <p><li><a name="ParamFormat"</a>
 * Format will be used to obtain an ImageWriter if one is not supplied. If
 * this parameter is null and Writer is non-null and has an originating
 * ImageWriterSpi, then the first format name listed by that provider will be
 * used. If Writer is null and Output is a File or a String, an attempt will
 * be made to derive the format name from the suffix of the file path. If
 * this fails, then the format will default to "PNG" as this is the most
 * versatile writer plug-in in the Java 2 core.
 * </li></p>
 * <p><li><a name="ParamUseProperties"</a>
 * If UseProperties is TRUE, then if stream or image metadata or thumbnails
 * are not provided as parameters, an attempt will be made to derive them
 * from the source image using the respective image properties previously
 * described for the "ImageRead" operation.
 * </li></p>
 * <p><li><a name="ParamTranscode"</a>
 * If Transcode is TRUE, then any stream or metadata derived either from
 * operation parameters or source image properties will be converted using
 * the ImageWriter's implementation of ImageTranscoder.
 * </li></p>
 * <p><li><a name="ParamVerifyOutput"</a>
 * If VerifyOutput is TRUE, then if the Output is a File or a String it will
 * be verified that a file at the specified location may either be overwritten
 * or created. If Output is a Socket, it will be verified that it is bound,
 * connected, not closed, and its write-half is not shut down. If any of
 * these checks fails, an exception will be thrown when the operation is
 * created. This parameter is ignored for other output types.
 * </li></p>
 * <p><li><a name="ParamAllowPixelReplacement"</a>
 * If AllowPixelReplacement is TRUE, and the ImageWriter supports pixel
 * replacement, then a construct will be enabled to allow "live" updating
 * of the output in response to RenderingChangeEvents or "InvalidRegion"
 * events.
 * </li></p>
 * <p><li><a name="ParamTileSize"</a>
 * TileSize specifies the desired tile size; it is used as defined by the
 * <a href="#RenderedModeTiling">tiling algorithm</a>. This parameter is
 * ignored if the ImageWriter does not support tiling.
 * Regardless of the capabilities of the writer, an exception will be thrown
 * when the operation is created if this parameter is non-null and either
 * its width or height is not positive.
 * </li></p>
 * <p><li><a name="ParamStreamMetadata"</a>
 * If StreamMetadata is non-null, then the parameter will take priority over
 * the corresponding image property as the source of stream metadata to
 * be written.
 * </li></p>
 * <p><li><a name="ParamImageMetadata"</a>
 * If ImageMetadata is non-null, then the parameter will take priority over
 * the corresponding image property as the source of image metadata to
 * be written.
 * </li></p>
 * <p><li><a name="ParamThumbnails"</a>
 * If Thumbnails is non-null, then the parameter will take priority over
 * the corresponding image property as the source of image thumbnails to
 * be written.
 * </li></p>
 * <p><li><a name="ParamListeners"</a>
 * Listeners will be used to set any progress or warning listeners of the
 * ImageWriter. Each element in the java.util.EventListener array will be
 * added for all types of listener it implements. For example if a listener
 * object implements all of the javax.imageio.event.IIOWrite*Listeners
 * interfaces then it will be added as a listener of each of the three types.
 * Any elements in the array which do not implement any of the
 * IIOWrite*Listeners will be ignored.
 * </li></p>
 * <p><li><a name="ParamLocale"</a>
 * Locale will be used to set the Locale of the ImageWriter. This parameter
 * overrides the equivalent setting of the Writer parameter if the latter is
 * also supplied.
 * </li></p>
 * <p><li><a name="ParamWriteParam"</a>
 * If WriteParam is null, an ImageWriteParam will be derived internally using
 * ImageWriter.getDefaultWriteParam().
 * </li></p>
 * <p><li><a name="ParamWriter"</a>
 * If Writer is null, an attempt will be made to find an ImageWriter capable
 * of writing the image. If this attempt to obtain an ImageWriter fails, an
 * exception will be thrown.
 * </li></p>
 * </ul>
 * </p>
 *
 * <h4><a name="SyncPolicy"</a>Parameters and Synchronization Policy</h4>
 *
 * Similarly to the case of any ImageReadParam or ImageReader supplied to the
 * "ImageRead" operation, any ImageWriteParam or ImageWriter supplied to the
 * "ImageWrite" operation is subject to modification within the operation
 * classes. A policy similar to the
 * <a href="./ImageReadDescriptor.html#SyncPolicy">"ImageRead"
 * synchronization policy</a> therefore applies as well for "ImageWrite".
 * 
 * <p>In the Sun Microsystems implementation of this operation these potential
 * conflicts have been mitigated to a certain extent:
 * 
 * <ul>
 * <p><li>
 * If the param is cloneable then it is cloned and the clone used internally.
 *  Otherwise if the param is an instance of ImageWriteParam itself rather than
 *   of a subclass thereof, i.e., getClass().getName() invoked on the param
 *   returns "javax.imageio.ImageWriteParam", then a new ImageWriteParam is
 *   constructed and the settings of the original param copied to it. If the
 *   param is not cloneable and is an instance of a proper subclass of
 *   ImageWriteParam then it is used directly.</li></p>
 * 
 * <p><li>
 * The only ImageWriter methods invoked after rendering are
 *   prepareReplacePixels(int,Rectangle), replacePixels(Raster,ImageWriteParam),
 *   and endReplacePixels() and these are invoked within a method synchronized
 *   on the ImageWriter object.</li></p>
 * </ul>
 * </p>
 *
 * <h3><a name="RenderedModeTiling"</a>Tiling</h3>
 *
 * The following algorithm is used to determine the tile size of the
 * image written to the output destination:
 * 
 * <pre>
 * if ImageWriter cannot write tiles
 *    output is untiled
 * else
 *    if TileSize parameter is non-null
 *       set tile size to TileSize
 *    else
 *       if WriteParam is null
 *          set tile size to source tile size
 *       else
 *          if tilingMode is ImageWriteParam.MODE_EXPLICIT
 *             if tile dimension is set in WriteParam
 *                set tile size to tile dimension from WriteParam
 *             else
 *                if preferred tile dimension is set in WriteParam
 * 		  set tile size to average of first two preferred dimensions
 *                else
 *                   set tile size to source tile size
 *          else // tilingMode is not ImageWriteParam.MODE_EXPLICIT
 *             the plug-in decides the tile size
 * </pre>
 * 
 * There is no mechanism to set the tile grid offsets of the output.
 *
 * <h3><a name="RenderedModePixelReplacement"</a>Pixel Replacement</h3>
 *
 * If AllowPixelReplacement is TRUE, the ImageWriter can replace pixels, and
 * the source is a PlanarImage, then the rendering of the operation
 * will respond to RenderingChangeEvents and Shape-valued PropertyChangeEvents
 * named "InvalidRegion". The rendering will be automatically registered as
 * a sink of the rendering of the operation node's source. As the source
 * rendering does not usually generate events, the calling code must also
 * explicitly register the "ImageWrite" rendering as a sink of the source
 * node. By whatever means the event is generated, when the rendering
 * receives such an event, it will determine the indices of all tiles which
 * overlap the invalid region and will replace the pixels of all these tiles
 * in the output.
 * 
 * <p>Note that this behavior differs from what would happen if the RenderedOp
 * created by the operation received a RenderingChangeEvent: in this case a
 * new rendering of the node would be created using the ParameterBlock and
 * RenderingHints currently in effect. This would cause the entire image to be
 * rewritten at the current position of the output. This will also happen
 * when AllowPixelReplacement is FALSE. In effect in both of these cases the
 * behavior in response to a RenderingChangeEvent is unspecified and the result
 * will likely be unexpected.</p>
 * 
 * <p>To avoid any inadvertent overwriting of the destination as a result of
 * events received by the RenderedOp, the following usage is recommended when
 * the objective is automatic pixel replacement:
 * 
 * <pre>
 *        // Sources, parameters, and hints.
 *        ParameterBlock args;
 *        RenderingHints hints;
 * 
 *        // Create the OperationNode.
 *        RenderedOp imageWriteNode = JAI.create("ImageWrite", args, hints);
 * 
 *        // Get the rendering which already exists due to "immediate" status.
 *        RenderedImage imageWriteRendering = imageWriteNode.getRendering();
 * 
 *        // Unhook the OperationNode as a sink of its source OperationNode.
 *        imageWriteNode.getSourceImage(0).removeSink(imageWriteNode);
 *
 *        // Add the rendering as a sink of the source OperationNode.
 *        imageWriteNode.getSourceImage(0).addSink(imageWriteRendering);
 * 
 *        // Free the OperationNode for garbage collection.
 *        imageWriteNode = null;
 * </pre>
 * 
 * At this point a reference to imageWriteRendering must be held as long as the
 * data of the source of the operation may change. Then provided the events are
 * correctly propagated to imageWriteRendering, the data in the output file
 * will be automatically updated to match the source data.</p>
 * 
 * <p>If pixel replacement is not the objective and inadvertent overwriting is
 * to be avoided then the safest approach would be the following:
 * 
 * <pre>
 *        // Create the OperationNode.
 *        RenderedOp imageWriteNode = JAI.create("ImageWrite", args, hints);
 * 
 *        // Unhook the OperationNode as a sink of its source
 *        imageWriteNode.getSourceImage(0).removeSink(imageWriteNode);
 * </pre>
 * 
 * The image is written by the first statement and no reference to the
 * rendering need be retained as before.
 *
 * <h3><a name="RenderedModeProperties"</a>Image Properties in Rendered Mode</h3>
 *
 * Image properties are used for metadata, thumbnails, and writer-related
 * information. The following properties may be set on the RenderedOp created
 * for the "ImageWrite" operation in rendered mode:
 * 
 * <p><table border=1>
 * <caption><b>Rendered Mode Image Properties</b></caption>
 * <tr>
 * <th>Property Name</th>
 * <th>Type</th>
 * <th>Comment</th>
 * </tr>
 * <tr>
 * <td>JAI.ImageWriteParam</td>
 * <td>ImageWriteParam</td>
 * <td>Set to ImageWriteParam actually used which may differ from the one passed in.</td>
 * </tr>
 * <tr>
 * <td>JAI.ImageWriter</td>
 * <td>ImageWriter</td>
 * <td>Set to ImageWriter actually used.</td>
 * </tr>
 * <tr>
 * <td>JAI.ImageMetadata</td>
 * <td>IIOMetadata</td>
 * <td>Set if and only if image metadata are available; may be transcoded.</td>
 * </tr>
 * <tr>
 * <td>JAI.StreamMetadata</td>
 * <td>IIOMetadata</td>
 * <td>Set if and only if stream metadata are available; may be transcoded.</td>
 * </tr>
 * <tr>
 * <td>JAI.Thumbnails</td>
 * <td>BufferedImage[]</td>
 * <td>Set if and only thumbnails are provided and the writer supportes writing them.</td>
 * </tr>
 * </table></p>
 * 
 * <p>If a given property is not set, this implies of course that the names of
 * absent properties will not appear in the array returned by getPropertyNames()
 * and getProperty() invoked to obtain absent properties will return
 * java.awt.Image.UndefinedProperty as usual.</p>
 * 
 * <p>The ImageWriter and ImageWriteParam may be used for subsequent invocations
 * of the operation or for informational purposes. Care should be taken in using
 * these property values with respect to the synchronization issues previously
 * discussed.</p>
 * 
 * <p>Metadata properties will be set to those actually written to the output. They
 * may be derived either from input parameters or source properties depending on
 * the values of the StreamMetadata, ImageMetadata, and UseProperties parameters.
 * They will be transcoded data if Transcode is TRUE and the ImageWriter supports
 * transcoding.</p>
 * 
 * <p>All properties will be set when the node is rendered.</p>
 *
 * <h2><a name="RenderableMode"</a>Renderable Mode</h2>
 *
 * In renderable mode the "ImageWrite" operation requires a
 * {@link java.awt.image.renderable.RenderableImage} source and writes a
 * {@link java.awt.image.RenderedImage} to the specified output destination.
 * As the "immediate" designation specified by {@link #isImmediate()}
 * has no effect in renderable mode, no image will be written without further
 * action by the calling code. To write an image, createRendering(),
 * createScaledRendering(), or createDefaultRendering()
 * must be invoked. Each of these will create a RenderedImage by forwarding the
 * createRendering() or equivalent call to the source image. The resulting
 * RenderedImage will be written to the output according to the
 * <a href="#RenderedMode">rendered mode</a> operation of "ImageWrite".
 * If a mapping of <code>JAI.KEY_INTERPOLATION</code> is supplied via a
 * <code>RenderingHints</code> passed to the operation, then the interpolation
 * type it specifies will be used to create the rendering if interpolation is
 * required.
 *
 * <h3><a name="RenderableModeParameters"</a>Renderable Mode Parameters</h3>
 *
 * The parameter list of the "ImageRead" operation in renderable mode is
 * identical to the <a href="#RenderedModeParameters">rendered mode
 * parameter list</a>.
 *
 * <h3>Pixel Replacement in Renderable Mode</h3>
 *
 * Pixel replacement pertains only to RenderedImages generated by rendering the
 * RenderableOp. It may occur if the same conditions apply as described for
 * pixel replacement in rendered mode. Due to the unspecified nature of the
 * underlying rendered sources of any rendering, this is not a recommended
 * procedure.
 *
 * <h3>Image Properties in Renderable Mode</h3>
 *
 * The RenderableOp node itself does not have any ImageWrite-related
 * properties. Any RenderedImages created by rendering the RenderableOp
 * (thereby writing an image to the output as described), may have
 * <a href="#RenderedModeProperties">rendered mode properties</a> set.
 *
 * <h2><a name="CollectionMode"</a>Collection Mode</h2>
 *
 * In collection mode the "ImageWrite" operation requires a
 * {@link java.util.Collection} source and writes its contents to the
 * specified output destination.
 *
 * <p>The Collection is treated as a sequence of images which will be
 * extracted from the Collection in the order returned by a new Iterator.
 * Elements in the Collection which are not RenderedImages will be ignored.
 * The derived sequence of images will then be written to the output.</p>
 * 
 * <p>If there is only one RenderedImage in the source Collection, this image
 * will be written as done in rendered mode operation. If there is more than
 * one RenderedImage, the sequence of RenderedImages will be written as an
 * image sequence. In the latter case the ImageWriter must be able to write
 * sequences.</p>
 *
 * <h3><a name="CollectionModeParameters"</a>Collection Mode Parameters</h3>
 *
 * Identical parameter list to rendered mode except:
 * 
 * <p><table border=1>
 * <caption><b>Collection Mode Parameter Differences</b></caption>
 * <tr><th>Name</th>           <th>Class Type</th>
 *                             <th>Default Value</th></tr>
 * <tr><td>ImageMetadata</td>  <td>javax.imageio.metadataIIOMetadata[]</td>
 *                             <td>null</td>
 * <tr><td>Thumbnails</td>     <td>java.awt.image.BufferedImage[][]</td>
 *                             <td>null</td>
 * </table></p>
 * 
 * <ul>
 * <p><li>
 * If the source is not a CollectionOp then the number of RenderedImages in
 * the source is counted. If it is not at least one then an exception is
 * thrown when the operation is created. If it is greater than one, then
 * the ImageWriter is checked to determine whether it can write sequences.
 * If it cannot then an exception is thrown when the operation is created.
 * </li></p>
 * <p><li>
 * The first index of the thumbnails array corresponds to the ordinal position
 * of the image in the collection and the second index to the thumbnails of
 * that image.
 * </li></p>
 * </ul>
 * 
 * <p>
 * The change to the ImageMetadata and Thumbnails parameters is that there can
 * now be a distinct image metadata object and thumbnail array for each image
 * in the Collection. The components of these respective arrays will be indexed
 * using the sequence of RenderedImages extracted from the source Collection by
 * the Iterator. It is the responsibility of the caller to ensure that this
 * sequencing is correct. In this context it is advisable to use a source
 * Collection which maintains the order of its elements such as a List.
 * </p>
 *
 * <h3>Pixel Replacement in Collection Mode</h3>
 *
 * If the value of the AllowPixelReplacement parameter is TRUE, then the
 * rendered Collection will contain RenderedImages which are registered as
 * listeners of their respective sources. Each image in the rendered Collection
 * will however be a rendering as opposed to a RenderedOp. This obviates the
 * need to unhook such a RenderedOp from its source as suggested. Two actions
 * on the part of the application are however necessary in this case:  1) the
 * sequence must be manually ended, and 2) the Collection node must be removed
 * as a sink of its source Collection. The first action is necessary as
 * pixels may be replaced at various times in various images in the sequence
 * and it is not possible to terminate the sequence at rendering time, and there
 * is no reliable mechanism to detect programmatically when this may later be
 * effected. The second action is necessary because a CollectionChangeEvent
 * received by the Collection node would cause the node to be re-rendered, i.e.,
 * the collection data to be rewritten using the current state of all parameters.
 * This will in fact also happen when AllowPixelReplacement is FALSE. In effect
 * in both of these cases the behavior in response to a CollectionChangeEvent
 * is unspecified and the result will likely be unexpected.
 * 
 * <p>
 * To ensure proper termination of the image sequence and avoid any inadvertent
 * overwriting of the destination as a result of events received by the
 * CollectionOp, the following usage is recommended when the objective is
 * automatic pixel replacement:
 * 
 * <pre>
 *        // Sources, parameters, and hints.
 *        ParameterBlock args;
 *        RenderingHints hints;
 * 
 *        // Create the Collection.
 *        CollectionImage imageWriteCollection =
 *            (CollectionImage)JAI.createCollection("ImageWrite", args, hints);
 * 
 *        // Unhook the Collection node from the source to avoid
 *        // re-renderings caused by CollectionChangeEvents.
 *        if(args.getSource(0) instanceof CollectionImage) {
 *            CollectionImage sourceCollection =
 * 	       (CollectionImage)args.getSource(0);
 *            sourceCollection.removeSink(imageWriteCollection);
 *        }
 * 
 *        // !!! Pixel replacement activity happens here ... !!!
 * 
 *        // Get the ImageWriter.
 *        ImageWriter writer =
 *            (ImageWriter)imageWriteCollection.getProperty("JAI.ImageWriter");
 * 
 *        // End the sequence if necessary.
 *        if(writer.canWriteSequence()) {
 *            writer.endWriteSequence();
 *        }
 * </pre>
 * </p>
 * 
 * <p>
 * Using the foregoing construct, all pixels in all images written to the output
 * sequence will remain current with the in-memory data of their respective
 * source provided all events are propagated as expected. Note that it is not
 * necessary to end the sequence manually if pixel replacement is not allowed or
 * is not supported. Also the sequence must be manually ended if and only if the
 * writer is capable of writing sequences. This permits pixel replacement to
 * work in the case where the source collection contains only a single image
 * and the writer supports pixel replacement but cannot write sequences.
 * </p>
 * 
 * <p>
 * If pixel replacement is not the objective, i.e., AllowPixelReplacement is
 * FALSE, and inadvertent overwriting is to be avoided then the safest approach
 * would be the following:
 * 
 * <pre>
 *        // Create the Collection.
 *        Collection imageWriteCollection =
 *            JAI.create("ImageWrite", args, hints);
 * 
 *        // Unhook the Collection node from the source to avoid
 *        // re-renderings caused by CollectionChangeEvents.
 *        if(args.getSource(0) instanceof CollectionImage) {
 *            CollectionImage sourceCollection =
 * 	       (CollectionImage)args.getSource(0);
 *            sourceCollection.removeSink(imageWriteCollection);
 *        }
 * </pre>
 * 
 * The image is written by the first statement and no reference to the
 * rendering need be retained.</p>
 *
 * <h3>Image Properties in Collection Mode</h3>
 *
 * Contingent on parameter settings and the presence of the appropriate
 * metadata, the rendered Collection may have the "JAI.StreamMetadata",
 * "JAI.ImageReadParam", and "JAI.ImageReader" properties set. Each
 * RenderedImage in the Collection may contain
 * <a href="#RenderedModeProperties">rendered mode properties</a>
 * contingent on parameter settings and data availability. Metadata
 * properties may be transcoded.
 *
 * @see javax.media.jai.OperationDescriptor
 * @see javax.imageio.ImageWriter
 * @see javax.imageio.ImageWriteParam
 * @see javax.imageio.metadata.IIOMetadata
 * @see javax.imageio.stream.ImageOutputStream
 */
public class ImageWriteDescriptor extends OperationDescriptorImpl {

    // Property name constants have package access for image factory use.

    /** ImageWriteParam property name "JAI.ImageWriteParam". */
    public static final String PROPERTY_NAME_IMAGE_WRITE_PARAM =
        "JAI.ImageWriteParam";

    /** ImageWriter property name "JAI.ImageWriter". */
    public static final String PROPERTY_NAME_IMAGE_WRITER =
        "JAI.ImageWriter";

    /**
     * Image metadata property name. Set to same value as
     * {@link ImageReadDescriptor#PROPERTY_NAME_METADATA_IMAGE}.
     */
    public static final String PROPERTY_NAME_METADATA_IMAGE =
        ImageReadDescriptor.PROPERTY_NAME_METADATA_IMAGE;

    /**
     * Stream metadata property name. Set to same value as
     * {@link ImageReadDescriptor#PROPERTY_NAME_METADATA_STREAM}.
     */
    public static final String PROPERTY_NAME_METADATA_STREAM =
        ImageReadDescriptor.PROPERTY_NAME_METADATA_STREAM;

    /**
     * Thumbnail property name. Set to same value as
     * {@link ImageReadDescriptor#PROPERTY_NAME_THUMBNAILS}.
     */
    public static final String PROPERTY_NAME_THUMBNAILS =
        ImageReadDescriptor.PROPERTY_NAME_THUMBNAILS;

    /**
     * Test method.
     *
     * @param args {inputFile, outputFile [, mode]}
     * @throws Throwable any error.
     */
    /* XXX
    public static void main(String[] args) throws Throwable {
        String inputFile = args[0];
        String outputFile = args[1];
        String modeName = args.length > 2 ?
            args[2] : RenderedRegistryMode.MODE_NAME;
        String formatName = args.length > 3 ?
            args[3] : null;

        ParameterBlock pb = new ParameterBlock();
        pb.set(new java.io.File(outputFile), 0);
        if(formatName != null) {
            pb.set(formatName, 1);
        }

        java.awt.image.RenderedImage[] images = null;
        if(modeName.equalsIgnoreCase(RenderedRegistryMode.MODE_NAME)) {
            pb.addSource(ImageIO.read(new java.io.File(inputFile)));
            images = new java.awt.image.RenderedImage[1];
            pb.set(new Dimension(128, 128), 6);
            images[0] = JAI.create("ImageWrite", pb, null);
            PrintProps.print((javax.media.jai.PropertySource)images[0]);
        } else if(modeName.equalsIgnoreCase(RenderableRegistryMode.MODE_NAME)) {
            ParameterBlock renderablePB = new ParameterBlock();
            renderablePB.addSource(ImageIO.read(new java.io.File(inputFile)));
            pb.addSource(javax.media.jai.JAI.createRenderable("renderable", renderablePB));
            java.awt.image.renderable.RenderableImage ri =
                JAI.createRenderable("ImageWrite", pb, null);
            PrintProps.print((javax.media.jai.PropertySource)ri);
            images = new java.awt.image.RenderedImage[1];
            //java.awt.image.renderable.RenderContext rc =
            //    new java.awt.image.renderable.RenderContext(
            //        new java.awt.geom.AffineTransform(42, 0, 0, 42, 0, 0));
            //images[0] = ri.createRendering(rc);
            images[0] = ri.createDefaultRendering();
            PrintProps.print((javax.media.jai.PropertySource)images[0]);
        } else if(modeName.equalsIgnoreCase(CollectionRegistryMode.MODE_NAME)) {
            java.util.ArrayList sourceCollection = new java.util.ArrayList();
            Object input =
                ImageIO.createImageInputStream(new java.io.File(inputFile));
            javax.imageio.ImageReader reader =
                (javax.imageio.ImageReader)ImageIO.getImageReaders(input).next();
            reader.setInput(input);
            int imageIndex = 0;
            do {
                try {
                    RenderedImage nextImage = reader.read(imageIndex);
                    sourceCollection.add(nextImage);
                } catch(IndexOutOfBoundsException e) {
                    break;
                }
                imageIndex++;
            } while(true);
            pb.addSource(sourceCollection);
            java.util.Collection imageCollection =
                JAI.createCollection("ImageWrite", pb, null);
            PrintProps.print((javax.media.jai.PropertySource)imageCollection);
            images = new java.awt.image.RenderedImage[imageCollection.size()];
            imageCollection.toArray(images);
        } else {
            throw new UnsupportedOperationException(modeName+" mode not supported");
        }

        final java.awt.Frame frame = new java.awt.Frame();
        frame.addWindowListener(new java.awt.event.WindowAdapter() {
                public void windowClosing(java.awt.event.WindowEvent e) {
                    frame.setEnabled(false);
                    frame.dispose();
                }
            });

        int gridSide = (int)(Math.sqrt(images.length) + 0.5);
        frame.setLayout(new java.awt.GridLayout(gridSide, gridSide));
        java.awt.Dimension screenSize =
            java.awt.Toolkit.getDefaultToolkit().getScreenSize();
        int width =
            Math.min(screenSize.width/gridSide, images[0].getWidth());
        int height =
            Math.min(screenSize.height/gridSide, images[0].getHeight());
        for(int i = 0; i < images.length; i++) {
            javax.media.jai.widget.ScrollingImagePanel panel =
                new javax.media.jai.widget.ScrollingImagePanel(images[i],
                                                               width, //image.getWidth(),
                                                               height);//image.getHeight());
            frame.add(panel);
        }
        frame.pack();
        frame.show();
    }
    */

    /**
     * The name of the operation.
     */
    private static final String OPERATION_NAME = "ImageWrite";

    /**
     * The resource strings that provide the general documentation and
     * specify the parameter list for the "ImageWrite" operation.
     */
    private static final String[][] resources = {
        {"GlobalName",  "ImageWrite"},
        {"LocalName",   "ImageWrite"},
        {"Vendor",      "com.sun.media.jai"},
        {"Description", I18N.getString("ImageWriteDescriptor0")},
        {"DocURL",      "http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/operator/ImageWriteDescriptor.html"},
        {"Version",     I18N.getString("DescriptorVersion")},
        {"arg0Desc",    I18N.getString("ImageWriteDescriptor1")},
        {"arg1Desc",    I18N.getString("ImageWriteDescriptor2")},
        {"arg2Desc",    I18N.getString("ImageWriteDescriptor3")},
        {"arg3Desc",    I18N.getString("ImageWriteDescriptor4")},
        {"arg4Desc",    I18N.getString("ImageWriteDescriptor5")},
        {"arg5Desc",    I18N.getString("ImageWriteDescriptor6")},
        {"arg6Desc",    I18N.getString("ImageWriteDescriptor7")},
        {"arg7Desc",    I18N.getString("ImageWriteDescriptor8")},
	{"arg8Desc",    I18N.getString("ImageWriteDescriptor9")},
	{"arg9Desc",    I18N.getString("ImageWriteDescriptor10")},
	{"arg10Desc",    I18N.getString("ImageWriteDescriptor11")},
	{"arg11Desc",    I18N.getString("ImageWriteDescriptor12")},
	{"arg12Desc",    I18N.getString("ImageWriteDescriptor13")},
	{"arg13Desc",    I18N.getString("ImageWriteDescriptor14")}
    };

    /** The parameter names for the "ImageWrite" operation. */
    private static final String[] paramNames = {
        "Output", "Format", "UseProperties", "Transcode",
        "VerifyOutput", "AllowPixelReplacement", "TileSize",
        "StreamMetadata", "ImageMetadata", "Thumbnails",
        "Listeners", "Locale", "WriteParam", "Writer"
    };

    /** The parameter class types for rendered mode of "ImageWrite". */
    private static final Class[] renderedParamClasses = {
        java.lang.Object.class,                   // Output
        java.lang.String.class,                   // Format
	java.lang.Boolean.class,                  // UseProperties
	java.lang.Boolean.class,                  // Transcode
	java.lang.Boolean.class,                  // VerifyOutput
	java.lang.Boolean.class,                  // AllowPixelReplacement
	java.awt.Dimension.class,                 // TileSize
	javax.imageio.metadata.IIOMetadata.class, // StreamMetadata
	javax.imageio.metadata.IIOMetadata.class, // ImageMetadata
        java.awt.image.BufferedImage[].class,     // Thumbnails
        java.util.EventListener[].class,          // Listeners
        java.util.Locale.class,                   // Locale
	javax.imageio.ImageWriteParam.class,      // WriteParam
	javax.imageio.ImageWriter.class           // Writer
    };

    /** The parameter default values for rendered mode of "ImageWrite". */
    private static final Object[] renderedParamDefaults = {
        NO_PARAMETER_DEFAULT, // Output
        null,                 // Format
        Boolean.TRUE,         // UseProperties
        Boolean.TRUE,         // Transcode
        Boolean.TRUE,         // VerifyOutput
        Boolean.FALSE,        // AllowPixelReplacement
        null,                 // TileSize
        null,                 // StreamMetadata
        null,                 // ImageMetadata
        null,                 // Thumbnails
        null,                 // Listeners
        null,                 // Locale
        null,                 // WriteParam
        null                  // Writer
    };

    /** The parameter class types for renderable mode of "ImageWrite". */
    private static final Class[] renderableParamClasses =
        renderedParamClasses;

    /** The parameter default values for renderable mode of "ImageWrite". */
    private static final Object[] renderableParamDefaults =
        renderedParamDefaults;

    /** The parameter class types for collection mode of "ImageWrite". */
    private static final Class[] collectionParamClasses = {
        java.lang.Object.class,                     // Output
        java.lang.String.class,                     // Format
	java.lang.Boolean.class,                    // UseProperties
	java.lang.Boolean.class,                    // Transcode
	java.lang.Boolean.class,                    // VerifyOutput
	java.lang.Boolean.class,                    // AllowPixelReplacement
	java.awt.Dimension.class,                   // TileSize
	javax.imageio.metadata.IIOMetadata.class,   // StreamMetadata
	javax.imageio.metadata.IIOMetadata[].class, // ImageMetadata
        java.awt.image.BufferedImage[][].class,     // Thumbnails
        java.util.EventListener[].class,            // Listeners
        java.util.Locale.class,                     // Locale
	javax.imageio.ImageWriteParam.class,        // WriteParam
	javax.imageio.ImageWriter.class             // Writer
    };

    /** The parameter default values for collection mode of "ImageWrite". */
    private static final Object[] collectionParamDefaults =
        renderedParamDefaults;

    /** Constructor. */
    public ImageWriteDescriptor() {
        super(resources,
              new String[] {RenderedRegistryMode.MODE_NAME,
                            RenderableRegistryMode.MODE_NAME,
                            CollectionRegistryMode.MODE_NAME},
              null, // sourceNames
              new Class[][] {{RenderedImage.class},
                             {RenderableImage.class},
                             {Collection.class}}, // sourceClasses
              paramNames,
              new Class[][] {renderedParamClasses,
                             renderableParamClasses,
                             collectionParamClasses},
              new Object[][] {renderedParamDefaults,
                              renderableParamDefaults,
                              collectionParamDefaults},
              new Object[][] {null, null, null}); // validParamValues
    }

    /**
     * Type-safe convenience method for creating a {@link RenderedOp}
     * representing the "ImageWrite" operation in rendered mode. The
     * method packs the source and parameters into a new
     * <code>ParameterBlock</code> and invokes
     * {@link JAI#create(String,ParameterBlock,RenderingHints)}.
     *
     * @param source The image to be written.
     * @param output The output destination.
     * @param format The format name of the output.
     * @param useProperties Whether to use image metadata properties as
     * fallbacks.
     * @param transcode Whether to transcode metadata before writing.
     * @param verifyOutput Whether to verify the validity of the output
     * destination.
     * @param allowPixelReplacement Whether to allow pixel replacement
     * in the output image.
     * @param tileSize The tile size of the output image.
     * @param streamMetadata Stream metadata to write to the output.
     * @param imageMetadata Image metadata to write to the output.
     * @param thumbnails Thumbnails to write to the output.
     * @param listeners EventListeners to be registered with the ImageWriter.
     * @param locale The Locale for the ImageWriter to use.
     * @param writeParam Java Image I/O write parameter instance.
     * @param writer Java Image I/O writer instance.
     * @param hints Operation hints.
     * @return a reference to the operation source.
     */
    public static RenderedOp create(RenderedImage source,
                                    ImageOutputStream output,
                                    String format,
                                    Boolean useProperties,
                                    Boolean transcode,
                                    Boolean verifyOutput,
                                    Boolean allowPixelReplacement,
                                    Dimension tileSize,
                                    IIOMetadata streamMetadata,
                                    IIOMetadata imageMetadata,
                                    BufferedImage[] thumbnails,
                                    EventListener[] listeners,
                                    Locale locale,
                                    ImageWriteParam writeParam,
                                    ImageWriter writer,
                                    RenderingHints hints) {

        ParameterBlock args = new ParameterBlock();

        args.addSource(source);

        args.add(output);
        args.add(format);
        args.add(useProperties);
        args.add(transcode);
        args.add(verifyOutput);
        args.add(allowPixelReplacement);
        args.add(tileSize);
        args.add(streamMetadata);
        args.add(imageMetadata);
        args.add(thumbnails);
        args.add(listeners);
        args.add(locale);
        args.add(writeParam);
        args.add(writer);

        return JAI.create(OPERATION_NAME, args, hints);
    }

    /**
     * Type-safe convenience method for creating a {@link Collection}
     * representing the "ImageWrite" operation in collection mode. The
     * method packs the source and parameters into a new
     * <code>ParameterBlock</code> and invokes
     * {@link JAI#createCollection(String,ParameterBlock,RenderingHints)}.
     *
     * @param source The collection to be written.
     * @param output The output destination.
     * @param format The format name of the output.
     * @param useProperties Whether to use image metadata properties as
     * fallbacks.
     * @param transcode Whether to transcode metadata before writing.
     * @param verifyOutput Whether to verify the validity of the output
     * destination.
     * @param allowPixelReplacement Whether to allow pixel replacement
     * in the output image.
     * @param tileSize The tile size of the output image.
     * @param streamMetadata Stream metadata to write to the output.
     * @param imageMetadata Image metadata to write to the output.
     * @param thumbnails Thumbnails to write to the output.
     * @param listeners EventListeners to be registered with the ImageWriter.
     * @param locale The Locale for the ImageWriter to use.
     * @param writeParam Java Image I/O write parameter instance.
     * @param writer Java Image I/O writer instance.
     * @param hints Operation hints.
     * @return a reference to the operation source.
     */
    public static Collection createCollection(Collection source,
                                              ImageOutputStream output,
                                              String format,
                                              Boolean useProperties,
                                              Boolean transcode,
                                              Boolean verifyOutput,
                                              Boolean allowPixelReplacement,
                                              Dimension tileSize,
                                              IIOMetadata streamMetadata,
                                              IIOMetadata[] imageMetadata,
                                              BufferedImage[][] thumbnails,
                                              EventListener[] listeners,
                                              Locale locale,
                                              ImageWriteParam writeParam,
                                              ImageWriter writer,
                                              RenderingHints hints) {

        ParameterBlock args = new ParameterBlock();

        args.addSource(source);

        args.add(output);
        args.add(format);
        args.add(useProperties);
        args.add(transcode);
        args.add(verifyOutput);
        args.add(allowPixelReplacement);
        args.add(tileSize);
        args.add(streamMetadata);
        args.add(imageMetadata);
        args.add(thumbnails);
        args.add(listeners);
        args.add(locale);
        args.add(writeParam);
        args.add(writer);

        return JAI.createCollection(OPERATION_NAME, args, hints);
    }

    /**
     * Type-safe convenience method for creating a {@link RenderableOp}
     * representing the "ImageWrite" operation in renderable mode. The
     * method packs the source and parameters into a new
     * <code>ParameterBlock</code> and invokes
     * {@link JAI#createRenderable(String,ParameterBlock,RenderingHints)}.
     *
     * @param source The renderable source to be written.
     * @param output The output destination.
     * @param format The format name of the output.
     * @param useProperties Whether to use image metadata properties as
     * fallbacks.
     * @param transcode Whether to transcode metadata before writing.
     * @param verifyOutput Whether to verify the validity of the output
     * destination.
     * @param allowPixelReplacement Whether to allow pixel replacement
     * in the output image.
     * @param tileSize The tile size of the output image.
     * @param streamMetadata Stream metadata to write to the output.
     * @param imageMetadata Image metadata to write to the output.
     * @param thumbnails Thumbnails to write to the output.
     * @param listeners EventListeners to be registered with the ImageWriter.
     * @param locale The Locale for the ImageWriter to use.
     * @param writeParam Java Image I/O write parameter instance.
     * @param writer Java Image I/O writer instance.
     * @param hints Operation hints.
     * @return a reference to the operation source.
     */
    public static RenderableOp createRenderable(RenderableImage source,
                                                ImageOutputStream output,
                                                String format,
                                                Boolean useProperties,
                                                Boolean transcode,
                                                Boolean verifyOutput,
                                                Boolean allowPixelReplacement,
                                                Dimension tileSize,
                                                IIOMetadata streamMetadata,
                                                IIOMetadata imageMetadata,
                                                BufferedImage[] thumbnails,
                                                EventListener[] listeners,
                                                Locale locale,
                                                ImageWriteParam writeParam,
                                                ImageWriter writer,
                                                RenderingHints hints) {
        ParameterBlock args = new ParameterBlock();

        args.addSource(source);

        args.add(output);
        args.add(format);
        args.add(useProperties);
        args.add(transcode);
        args.add(verifyOutput);
        args.add(allowPixelReplacement);
        args.add(tileSize);
        args.add(streamMetadata);
        args.add(imageMetadata);
        args.add(thumbnails);
        args.add(listeners);
        args.add(locale);
        args.add(writeParam);
        args.add(writer);

        return JAI.createRenderable(OPERATION_NAME, args, hints);
    }

    /**
     * Returns true indicating that the operation should be rendered
     * immediately during a call to <code>JAI.create[]()</code> or
     * <code>JAI.createCollection[NS]()</code>.
     *
     * @see javax.media.jai.OperationDescriptor
     */
    public boolean isImmediate() {
        return true;
    }

    /**
     * Validates the parameters in the supplied <code>ParameterBlock</code>.
     *
     * <p>In addition to the standard validation performed by the
     * corresponding superclass method, this method verifies the following:
     * <ul>
     * <li>if <i>VerifyOutput</i> is <code>TRUE</code> and <i>Output</i>
     * is a <code>File</code> or <code>String</code>, whether the
     * corresponding physical file is writable, i.e., exists and may
     * be overwritten or does not exist and may be created; and</li>
     * <li>if <i>VerifyOutput</i> is <code>TRUE</code> and <i>Output</i>
     * is a <code>Socket</code>, whether it is bound, connected, open,
     * and the write-half is not shut down; and</li>
     * <li>if in collection mode (<code>modeName</code> equals
     * {@link CollectionRegistryMode#MODE_NAME}), the source is not a
     * {@link CollectionOp}, and the size of the source
     * {@link Collection} is greater than unity, whether the
     * {@link ImageWriter} <i>cannot</i> write sequences.</li>
     * </ul>
     *
     * If the superclass method finds that the arguments are invalid, or if
     * this method determines that any of the foregoing conditions is true,
     * an error message will be appended to <code>msg</code> and
     * <code>false</code> will be returned; otherwise <code>true</code> will
     * be returned.</p>
     *
     * @param modeName The operation mode.
     * @param args The source and parameters of the operation.
     * @param msg A container for any error messages.
     *
     * @return Whether the supplied parameters are valid.
     */
    protected boolean validateParameters(String modeName,
                                         ParameterBlock args,
                                         StringBuffer msg) {
        if (!super.validateParameters(modeName, args, msg)) {
            return false;
        }

        // Get the Output parameter.
        Object output = args.getObjectParameter(0);

        // Check the output if so requested by "VerifyOutput".
	Boolean verifyOutput = (Boolean)args.getObjectParameter(4);
	if (verifyOutput.booleanValue()){
            if(output instanceof File || output instanceof String) {
                // Set file and path variables.
                File file = null;
                String path = null;
                if(output instanceof File) {
                    file = (File)output;
                    path = file.getPath();
                } else if(output instanceof String) {
                    path = (String)output;
                    file = new File(path);
                }

                // Perform non-destructive test that the file
                // may be created and written.
                try {
                    if (file.exists()) {
                        if (!file.canWrite()) {
                            // Cannot write to existing file.
                            msg.append(file.getPath() + " " +
                                       I18N.getString("ImageWriteDescriptor15"));
                            return false;
                        }
                    } else {
                        if (!file.createNewFile()) {
                            // Cannot create file.
                            msg.append(file.getPath() + " " +
                                       I18N.getString("ImageWriteDescriptor16"));
                            return false;
                        }
                        file.delete();
                    }
                } catch (IOException ioe) {
                    // I/O exception during createNewFile().
                    msg.append(file.getPath() + " " +
                               I18N.getString("ImageWriteDescriptor17") + " " +
                               ioe.getMessage());
                    return false;
                } catch (SecurityException se) {
                    // Security exception during exists(), canWrite(),
                    // createNewFile(), or delete().
                    msg.append(file.getPath() + " " +
                               I18N.getString("ImageWriteDescriptor18") + " " +
                               se.getMessage());
                    return false;
                }
            } else if(output instanceof Socket) {
                Socket socket = (Socket)output;

                if(socket.isOutputShutdown()) {
                    msg.append("\"" + socket + "\": " + 
                               I18N.getString("ImageWriteDescriptor19"));
                    return false;
                } else if(socket.isClosed()) {
                    msg.append("\"" + socket + "\": " + 
                               I18N.getString("ImageWriteDescriptor20"));
                    return false;
                } else if(!socket.isBound()) {
                    msg.append("\"" + socket + "\": " + 
                               I18N.getString("ImageWriteDescriptor21"));
                    return false;
                } else if(!socket.isConnected()) {
                    msg.append("\"" + socket + "\": " + 
                               I18N.getString("ImageWriteDescriptor22"));
                    return false;
                }
            }
        }

        // Get the Format parameter.
        String format = (String)args.getObjectParameter(1);

        // Get the ImageWriter parameter.
        ImageWriter writer = (ImageWriter)args.getObjectParameter(13);

        if(format == null) {
            // Attempt to get the format from the ImageWriter provider.
            if(writer != null) {

                // Get the SPI.
                ImageWriterSpi spi = writer.getOriginatingProvider();

                // Set from the SPI.
                if(spi != null) {
                    format = spi.getFormatNames()[0];
                }
            }

            // Attempt to deduce the format from the file suffix.
            if(format == null &&
               (output instanceof File || output instanceof String)) {

                // Set the file name string.
                String name = output instanceof File ?
                    ((File)output).getName() : (String)output;

                // Extract the suffix.
                String suffix = name.substring(name.lastIndexOf(".") + 1);

                // Get the writers of that suffix.
                Iterator writers = ImageIO.getImageWritersBySuffix(suffix);

                if(writers != null) {
                    // Get the first writer.
                    writer = (ImageWriter)writers.next();

                    if(writer != null) {
                        // Get the SPI.
                        ImageWriterSpi spi = writer.getOriginatingProvider();

                        // Set from the SPI.
                        if(spi != null) {
                            format = spi.getFormatNames()[0];
                        }
                    }
                }
            }

            // Default to the most versatile core Java Image I/O writer.
            if(format == null) {
                format = "PNG";
            }

            // Replace the format setting.
            if(format != null) {
                args.set(format, 1);
            }
        }

        // Check the tile size parameter if present.
        Dimension tileSize = (Dimension)args.getObjectParameter(6);
        if(tileSize != null && (tileSize.width <= 0 || tileSize.height <= 0)) {
            msg.append(I18N.getString("ImageWriteDescriptor23"));
            return false;
        }

        // For collection mode, verify that the source collection contains
        // at least one RenderedImage and that the writer can handle sequences
        // if there is more than one RenderedImage in the source collection.
        if(modeName.equalsIgnoreCase(CollectionRegistryMode.MODE_NAME)) {
            // Get the source collection.
            Collection source = (Collection)args.getSource(0);

            // If the source collection is a CollectionOp do not perform this
            // check as invoking source.size() will render the node.
            if(!(source instanceof CollectionOp)) {

                // Determine the number of RenderedImages in the Collection.
                int numRenderedImages = 0;
                Iterator iter = source.iterator();
                while(iter.hasNext()) {
                    if(iter.next() instanceof RenderedImage) {
                        numRenderedImages++;
                    }
                }

                if(numRenderedImages == 0) {
                    msg.append(I18N.getString("ImageWriteDescriptor24"));
                    return false;
                } else if(numRenderedImages > 1) {
                    // Get the writer parameter.
                    writer = (ImageWriter)args.getObjectParameter(13);

                    // If the parameter writer is null, get one based on the
                    // format.
                    if(writer == null && format != null) {
                        // Get the writers of that format.
                        Iterator writers =
                            ImageIO.getImageWritersByFormatName(format);

                        if(writers != null) {
                            // Get the first writer.
                            writer = (ImageWriter)writers.next();
                        }
                    }

                    if(writer != null) {
                        // Check that the writer can write sequences.
                        if(!writer.canWriteSequence()) {
                            msg.append(I18N.getString("ImageWriteDescriptor25"));
                            return false;
                        }
                    }
                }
            }
        }

        return true;
    }
}
