/* 
 * JGimp - A Java extension for the GIMP
 * Copyright (C) 2002  Georgia Tech Research Corp.
 * Written by Michael Terry, mterry@cc.gatech.edu
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.gimp.jgimp;

import java.awt.Color;
import java.awt.Rectangle;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Vector;

import org.gimp.jgimp.extension.JGimpExtension;
import org.gimp.jgimp.nativewrappers.JGimpColor;
import org.gimp.jgimp.nativewrappers.JGimpEnd;
import org.gimp.jgimp.nativewrappers.JGimpFloat;
import org.gimp.jgimp.nativewrappers.JGimpFloatArray;
import org.gimp.jgimp.nativewrappers.JGimpInt16;
import org.gimp.jgimp.nativewrappers.JGimpInt16Array;
import org.gimp.jgimp.nativewrappers.JGimpInt32;
import org.gimp.jgimp.nativewrappers.JGimpInt32Array;
import org.gimp.jgimp.nativewrappers.JGimpInt8;
import org.gimp.jgimp.nativewrappers.JGimpInt8Array;
import org.gimp.jgimp.nativewrappers.JGimpPDBStatus;
import org.gimp.jgimp.nativewrappers.JGimpParasite;
import org.gimp.jgimp.nativewrappers.JGimpString;
import org.gimp.jgimp.nativewrappers.JGimpStringArray;
import org.gimp.jgimp.plugin.JGimpParamDescriptor;
import org.gimp.jgimp.plugin.JGimpPlugIn;
import org.gimp.jgimp.plugin.JGimpProcedureDescriptor;
import org.gimp.jgimp.proxy.JGimpProxy;
import org.gimp.jgimp.proxy.JGimpProxyException;

/**
 * GimpApp provides Java-friendly access to the capabilities of the GIMP.
 * It is the preferred way of communicating with the GIMP (the other
 * way is with a {@link org.gimp.jgimp.proxy.JGimpProxy} object, which
 * provides the lowest-level Java access, but which is not very forgiving).
 * <p>
 * Plug-ins or extensions are passed a GimpApp object automatically when
 * invoked.
 * <p>
 * Important note on <b>calling GIMP procedures</b> with GimpApp objects: If you have
 * programmed the GIMP before, you know that when you call a procedure in the PDB,
 * a list of values is returned, and within this list, the first value is always
 * a "status" value indicating whether the procedure executed without error.
 * With GimpApp objects, you do not have this status value: GimpApp automatically strips
 * it away after looking at it. If the value is anything other than PDB_SUCCESS, it throws an
 * exception (which is the Java way to handle error conditions). Thus, the array of
 * data returned after invoking a procedure is always just the procedure's return data,
 * and nothing else. {@link org.gimp.jgimp.proxy.JGimpProxy} does not strip this value,
 * so if you use {@link org.gimp.jgimp.proxy.JGimpProxy} objects,
 * be aware of this difference.
 * <p>
 * Important note on <b>using arrays</b> with GimpApp objects: Since the GIMP is coded
 * using C, it needs to know the number of elements in an array. Thus, when you see
 * listings in the PDB for parameters taking arrays, the value preceeding the array is always an
 * integer to indicate the length of the array. Since array lengths are built-in to the
 * data type in Java, it is unnecessary and error-prone to require the Java programmer
 * to adopt this convention. Thus, GimpApp objects automatically add array lengths when
 * you pass in an array to a PDB procedure call -- just omit the array count altogether,
 * ignoring that parameter. Likewise, the array count is automatically removed when an
 * array is returned by a PDB procedure call. {@link org.gimp.jgimp.proxy.JGimpProxy}
 * objects do not perform this conversion, so note the difference if you are using a
 * J{@link org.gimp.jgimp.proxy.JGimpProxy} object.
 */
public class GimpApp {

    private JGimpProxy m_Proxy = null;
    private HashMap m_ProcedureDescriptionCache = new HashMap();

    /**
     * Constructs a new GimpApp object to communicate with the GIMP
     * using the given proxy.
     * @param proxy The proxy used to communicate with the GIMP
     */
    public GimpApp(JGimpProxy proxy) {
        m_Proxy = proxy;
    }

    /**
     * Returns the proxy used by this object to communicate with the GIMP.
     */
    public JGimpProxy getProxy() {
        return m_Proxy;
    }

    /**
     * Instructs the GIMP to create a new RGB image.
     * @param width  The width of the image to create
     * @param height The height of the image to create
     * @pdb gimp-image-new
     * @return Reference to a new GIMP image
     * @throws JGimpException
     */
    public JGimpImage createRGBImage(int width, int height) throws JGimpException {
        return createImage(width, height, 0);
    }

    /**
     * Instructs the GIMP to create a new grayscale image.
     * @param width  The width of the image to create
     * @param height The height of the image to create
     * @pdb gimp-image-new
     * @return Reference to a new GIMP image
     * @throws JGimpException
     */
    public JGimpImage createGrayscaleImage(int width, int height) throws JGimpException {
        return createImage(width, height, 1);
    }

    /**
     * Instructs the GIMP to create a new indexed image.
     * @param width  The width of the image to create
     * @param height The height of the image to create
     * @pdb gimp-image-new
     * @return Reference to a new GIMP image
     * @throws JGimpException
     */
    public JGimpImage createIndexedImage(int width, int height) throws JGimpException {
        return createImage(width, height, 2);
    }

    /**
     * Instructs the GIMP to create a new image.
     * @param width  The width of the image to create
     * @param height The height of the image to create
     * @param type   The type of image to create (must be a valid integer
     *               listed by GIMP's PDB)
     * @pdb gimp-image-new
     * @return Reference to a new GIMP image
     * @throws JGimpException
     */
    public JGimpImage createImage(int width, int height, int type) throws JGimpException {
        return (JGimpImage) this.callProcedure("gimp_image_new", new JGimpInt32(width), new JGimpInt32(height), new JGimpInt32(type))[0];
    }

    /**
     * Takes the ID of an existing GIMP image and creates a {@link JGimpImage}
     * object for that image. This method should only be used when a function
     * returns an image as an integer, rather than as a JGimpImage. This usually 
     * happens when returning an array of images. You <em>cannot</em> use an
     * arbitrary ID or create your own -- the GIMP will just choke on the value.
     * @param imageID The ID of an existing GIMP image
     * @return A JGimpImage object representing the given image
     * @throws JGimpException
     */
    public JGimpImage wrapExistingImage(int imageID) throws JGimpException {
        return new JGimpImage(this, imageID);
    }

    /**
     * Returns an array of all images currently open in the GIMP.
     * @return All images currently open in the GIMP
     * @pdb gimp-image-list
     * @throws JGimpException
     */
    public JGimpImage[] getAllOpenImages() throws JGimpException {
        int[] images = this.callProcedure("gimp_image_list")[0].convertToIntArray();
        JGimpImage[] returnArray = new JGimpImage[images.length];
        for (int i = 0; i < returnArray.length; i++) {
            returnArray[i] = this.wrapExistingImage(images[i]);
        }
        return returnArray;
    }

    /*
        public GPaintSettings getPalette() throws JGimpProxyException {
        }
    */

    /**
     * Opens the image with the given filename, and returns a reference to the opened
     * image. The function will open any image that the GIMP can read/open.
     * @param The image's file name
     * @pdb gimp-file-load
     * @return Reference to the newly opened image
     * @throws JGimpException
     */
    public JGimpImage openImage(String inFileName) throws JGimpException {
        return (JGimpImage) this.callProcedure("gimp_file_load", new JGimpInt32(1), inFileName, inFileName)[0];
    }

    /**
     * Instructs the GIMP to quit.
     * @pdb gimp-quit
     * @throws JGimpException
     */
    public void quitGimp() throws JGimpException {
        this.callProcedure("gimp_quit", new JGimpInt32(0));
    }

    /**
     * Forces a refresh of all open GIMP windows (not Java windows).
     * @pdb gimp-displays-flush
     * @throws JGimpException
     */
    public void flushDisplays() throws JGimpException {
        this.callProcedure("gimp_displays_flush");
    }

    /**
     * This method provides similar functionality to a Java property file
     * (which holds key/value pairs), but is managed by the GIMP.
     * Since the GIMP manages it, other plug-ins and scripts can
     * use the same key/value pairs.
     * @param key The key for the resource data you wish to get
     * @return    The value for the given key, null if no value is set.
     * @pdb gimp-gimprc-query
     * @throws JGimpException
     */
    public String getResourceFileData(String key) throws JGimpException {
        String returnString = null;
        try {
            returnString = this.callProcedure("gimp_gimprc_query", key)[0].convertToString();
        } catch (JGimpProcedureExecutionException e) {
            ; // ignore -- execution exception means there is no such value
        }
        return returnString;
    }
    
    /**
     * This method provides similar functionality to a Java property file
     * (which holds key/value pairs), but is managed by the GIMP.
     * Since the GIMP manages it, other plug-ins and scripts can
     * use the same key/value pairs.
     * @param key  The key for the resource data you wish to set
     * @param data The value for the given key
     * @pdb gimp-gimprc-set
     * @throws JGimpException
     */
    public void setResourceFileData(String key, String data) throws JGimpException {
        this.callProcedure("gimp_gimprc_set", key, data);
    }

    /**
     * Clears the cache of procedure descriptions. Should only
     * be used after procedures have been unloaded and reloaded
     * in the GIMP.
     * @see GimpApp#getProcedureDescription(String)
     */
    public void clearProcedureDescriptionCache() {
        m_ProcedureDescriptionCache.clear();
    }
    
    /**
     * Returns a description of the procedure with the given name.
     * Descriptions are cached. If a procedure is unloaded and reloaded
     * with different values, the data will be incorrect unless the 
     * cache is cleared before calling this method.
     * @return An object holding the procedure's description.
     * @see GimpApp#clearProcedureDescriptionCache()
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpException
     */
    public JGimpProcedureDescriptor getProcedureDescription(String name) throws JGimpProcedureNotFoundException, JGimpException {
        name = name.replace('-', '_');
        if (m_ProcedureDescriptionCache.containsKey(name)) {
            return (JGimpProcedureDescriptor)m_ProcedureDescriptionCache.get(name);
        }
        JGimpProcedureDescriptor[] resultArray = getAllProcedureDescriptions(name, ".*", ".*", ".*", ".*", ".*", ".*");
        for (int i = 0; i < resultArray.length; i++) {
            if (resultArray[i].getName().equals(name)) {
                m_ProcedureDescriptionCache.put(name, resultArray[i]);
                return resultArray[i];
            }
        }
        throw new JGimpProcedureNotFoundException(name);
    }

    /**
     * Searches the PDB for procedures matching the given regular expressions,
     * returning an array of JGimpProcedureDescriptor objects for each match. Any
     * of the expressions can be ".*", meaning they'll match on anything. If the
     * expression is <em>null</em> or an empty string, it will default to ".*". 
     * @param inNameRegEx      A regular expression to match on the procedure's name
     * @param inBlurbRegEx     A regular expression to match on the procedure's blurb
     * @param inHelpRegEx      A regular expression to match on the procedure's help comment
     * @param inAuthorRegEx    A regular expression to match on the procedure's author
     * @param inCopyrightRegEx A regular expression to match on the procedure's copyright statement
     * @param inDateRegEx      A regular expression to match on the procedure's help comment
     * @param inProcTypeRegEx  A regular expression to match on the procedure's type
     * @return An array of procedures matching the given expressions
     * @throws JGimpException
     * @pdb gimp-procedural-db-query
     * @pdb gimp-procedural-db-proc-info
     * @pdb gimp-procedural-db-proc-arg
     * @pdb gimp-procedural-db-proc-val
     */
    public JGimpProcedureDescriptor[] getAllProcedureDescriptions(
        String inNameRegEx,
        String inBlurbRegEx,
        String inHelpRegEx,
        String inAuthorRegEx,
        String inCopyrightRegEx,
        String inDateRegEx,
        String inProcTypeRegEx)
        throws JGimpException {

        // We have to manually look everything up because callProcedure calls us
        // to make sure the data types match up
        if ((inNameRegEx == null) || (inNameRegEx.length() < 1)) {
            inNameRegEx = ".*";
        }
        if ((inBlurbRegEx == null) || (inBlurbRegEx.length() < 1)) {
            inBlurbRegEx = ".*";
        }
        if ((inHelpRegEx == null) || (inHelpRegEx.length() < 1)) {
            inHelpRegEx = ".*";
        }
        if ((inAuthorRegEx == null) || (inAuthorRegEx.length() < 1)) {
            inAuthorRegEx = ".*";
        }
        if ((inCopyrightRegEx == null) || (inCopyrightRegEx.length() < 1)) {
            inCopyrightRegEx = ".*";
        }
        if ((inDateRegEx == null) || (inDateRegEx.length() < 1)) {
            inDateRegEx = ".*";
        }
        if ((inProcTypeRegEx == null) || (inProcTypeRegEx.length() < 1)) {
            inProcTypeRegEx = ".*";
        }
        JGimpData[] returnResults =
            m_Proxy.runPDBProcedure(
                "gimp_procedural_db_query",
                new JGimpData[] {
                    new JGimpString(inNameRegEx),
                    new JGimpString(inBlurbRegEx),
                    new JGimpString(inHelpRegEx),
                    new JGimpString(inAuthorRegEx),
                    new JGimpString(inCopyrightRegEx),
                    new JGimpString(inDateRegEx),
                    new JGimpString(inProcTypeRegEx)});
                    
        if ((!(returnResults[0] instanceof JGimpPDBStatus)) || (returnResults[0].convertToInt() != JGimpPDBStatus.Type.GIMP_PDB_SUCCESS)) {
            throw new JGimpProcedureExecutionException("gimp_procedural_db_query", "Non-success return status: " + returnResults[0].convertToInt());
        }
        String[] matches = returnResults[2].convertToStringArray();

        JGimpProcedureDescriptor[] procDescriptors = new JGimpProcedureDescriptor[matches.length];
        for (int i = 0; i < matches.length; i++) {
            String procedureName = matches[i];
            returnResults = m_Proxy.runPDBProcedure("gimp_procedural_db_proc_info", new JGimpData[] {new JGimpString(procedureName)});
            if ((!(returnResults[0] instanceof JGimpPDBStatus)) || (returnResults[0].convertToInt() != JGimpPDBStatus.Type.GIMP_PDB_SUCCESS)) {
                throw new JGimpProcedureExecutionException("gimp_procedural_db_query", "Non-success return status: " + returnResults[0].convertToInt());
            }
            String blurb = returnResults[1].convertToString();
            String help = returnResults[2].convertToString();
            String author = returnResults[3].convertToString();
            String copyright = returnResults[4].convertToString();
            String date = returnResults[5].convertToString();
            int procType = returnResults[6].convertToInt();
            int numArgs = returnResults[7].convertToInt();
            int numReturnVals = returnResults[8].convertToInt();
            
            JGimpParamDescriptor[] procArgs = new JGimpParamDescriptor[numArgs];
            for (int j = 0; j < procArgs.length; j++) {
                returnResults = m_Proxy.runPDBProcedure("gimp_procedural_db_proc_arg", new JGimpData[] {new JGimpString(procedureName), new JGimpInt32(j)});
                if ((!(returnResults[0] instanceof JGimpPDBStatus)) || (returnResults[0].convertToInt() != JGimpPDBStatus.Type.GIMP_PDB_SUCCESS)) {
                    throw new JGimpProcedureExecutionException("gimp_procedural_db_query", "Non-success return status: " + returnResults[0].convertToInt());
                }
                int argType = returnResults[1].convertToInt();
                String argName = returnResults[2].convertToString();
                String argDescription = returnResults[3].convertToString();
                procArgs[j] = new JGimpParamDescriptor(argType, argName, argDescription);
            }
            
            // Prune array counts
            Vector prunedArgs = new Vector();
            for (int j = 0; j < procArgs.length; j++) {
                if ((j+1) < procArgs.length) {
                    if (JGimpPDBArgTypeConstants.isArrayType(procArgs[j+1].getGimpPDBArgType())) {
                        continue;
                    }
                }
                prunedArgs.add(procArgs[j]);
            }

            JGimpParamDescriptor[] procReturnVals = new JGimpParamDescriptor[numReturnVals];
            for (int j = 0; j < procReturnVals.length; j++) {
                returnResults = m_Proxy.runPDBProcedure("gimp_procedural_db_proc_val", new JGimpData[] {new JGimpString(procedureName), new JGimpInt32(j)});
                if ((!(returnResults[0] instanceof JGimpPDBStatus)) || (returnResults[0].convertToInt() != JGimpPDBStatus.Type.GIMP_PDB_SUCCESS)) {
                    throw new JGimpProcedureExecutionException("gimp_procedural_db_query", "Non-success return status: " + returnResults[0].convertToInt());
                }
                int valType = returnResults[1].convertToInt();
                String valName = returnResults[2].convertToString();
                String valDescription = returnResults[3].convertToString();
                procReturnVals[j] = new JGimpParamDescriptor(valType, valName, valDescription);
            }

            // Prune array counts
            Vector prunedReturnVals = new Vector();
            for (int j = 0; j < procReturnVals.length; j++) {
                if ((j+1) < procReturnVals.length) {
                    if (JGimpPDBArgTypeConstants.isArrayType(procReturnVals[j+1].getGimpPDBArgType())) {
                        continue;
                    }
                }
                prunedReturnVals.add(procReturnVals[j]);
            }

            JGimpParamDescriptor[] prunedArgArray = new JGimpParamDescriptor[prunedArgs.size()];
            prunedArgs.toArray(prunedArgArray);
            JGimpParamDescriptor[] prunedReturnValArray = new JGimpParamDescriptor[prunedReturnVals.size()];
            prunedReturnVals.toArray(prunedReturnValArray);
            procDescriptors[i] = new JGimpProcedureDescriptor(procedureName, blurb, help, author, copyright, date, null, null, prunedArgArray, prunedReturnValArray);
        }
        return procDescriptors;
    }

    /**
     * Returns a {@link JGimpProcedure} object of the procedure with the given name.
     * Any regular expressions recognized by the GIMP can be used within the
     * given procedure name, but only the first match found is returned.
     * @param name The name of the procedure to get
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpException
     */
    public JGimpProcedure getProcedure(String name) throws JGimpProcedureNotFoundException, JGimpException {
        JGimpProcedureDescriptor description = this.getProcedureDescription(name);
        return new JGimpProcedure(this, description);
    }

    /**
     * Convenience method for calling a procedure that
     * takes no arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(String procedureName)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[0]);
    }
    /**
     * Convenience method for calling a procedure that
     * takes one argument. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(String procedureName, Object arg1)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes two arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(String procedureName, Object arg1, Object arg2)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes three arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(String procedureName, Object arg1, Object arg2, Object arg3)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2, arg3 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes four arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(String procedureName, Object arg1, Object arg2, Object arg3, Object arg4)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2, arg3, arg4 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes five arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(String procedureName, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2, arg3, arg4, arg5 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes six arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(String procedureName, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes seven arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(String procedureName, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes eight arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(
        String procedureName,
        Object arg1,
        Object arg2,
        Object arg3,
        Object arg4,
        Object arg5,
        Object arg6,
        Object arg7,
        Object arg8)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes nine arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(
        String procedureName,
        Object arg1,
        Object arg2,
        Object arg3,
        Object arg4,
        Object arg5,
        Object arg6,
        Object arg7,
        Object arg8,
        Object arg9)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 });
    }
    /**
     * Convenience method for calling a procedure that
     * takes ten arguments. Calls callProcedure(String, Object[]).
     *
     * @throws JGimpProxyException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpException
     * 
     * @see GimpApp#callProcedure(String, Object[])
     */
    public JGimpData[] callProcedure(
        String procedureName,
        Object arg1,
        Object arg2,
        Object arg3,
        Object arg4,
        Object arg5,
        Object arg6,
        Object arg7,
        Object arg8,
        Object arg9,
        Object arg10)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {
        return callProcedure(procedureName, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 });
    }
    /**
     * A Java-friendly method for calling GIMP procedures.
     * This method takes a procedure name and an array of Java objects
     * and attempts to coerce those Java objects into the expected
     * parameter types for the procedure.
     * <p>
     * When JGimp communicates with the GIMP, it transports data using
     * JGimpData objects, which wrap basic data types (ints, doubles, strings,
     * etc.), and GIMP objects (images, layers, etc.). This procedure attempts
     * to coerce data into these JGimpData wrapper objects. For example,
     * if an integer is required for a parameter, it can convert a
     * java.lang.Integer into the proper JGimpData type (JGimpInt32).
     * <p>
     * This method first performs a look-up on the procedure to discover
     * its parameter types. It then performs data conversions for any objects
     * not already properly wrapped in the expected JGimpData types.
     * <p>
     * <em>Note:</em> Arrays should not have their length sent as a parameter.
     * In a procedure's description, GIMP's PDB will list array types,
     * <em>preceded</em> by their length. Since Java has this information built-in,
     * you <em>do not</em> need to specify array lengths. Instead, you should
     * ignore these parameters and not include them; they will automatically be
     * added for you. Similarly, returned data <em>does not</em> include array
     * lengths when arrays are returned. These conditions apply only to invoking
     * procedures through GimpApp objects, and do not hold true if you use the
     * lower-level JGimpProxy object to execute procedures.
     * <p>
     * Common mistake: If a procedure specifies an array for a parameter, you
     * should pass in the array <em>wrapped</em> in another array; don't make
     * the mistake of passing in the array for the commandArgs parameter to
     * this method. For example, if a GIMP procedure takes a single parameter
     * which is an array (which would actually be two parameters in the PDB
     * because of the array length -- see note above), then you must create
     * a new array with a single
     * element -- the array data you want to pass in. Example:
     * gimpApp.callProcedure("foo", new Object[] { myArray } )
     * <p>
     * The returned data are native Java types where possible, org.gimp.jgimp objects
     * when not possible (e.g. {@link JGimpImage}, {@link JGimpLayer}, {@link JGimpDrawable}, etc.).
     * The return values do not include any status objects, just the real
     * data stuff you're expecting. If an error is encountered, an exception is thrown.
     * @param procedureName The procedure to call
     * @param commandArgs The procedure's arguments. These are automatically
     *                     inserted by inspecting the length of the array you pass in
     * @return An array of Java types where possible, org.gimp.jgimp objects when
     *          no corresponding Java type exists.
     * @throws JGimpException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpProcedureExecutionException
     * @throws JGimpProcedureNotFoundException
     * @throws JGimpProxyException
     */
    public JGimpData[] callProcedure(String procedureName, Object[] commandArgs)
        throws
            JGimpProxyException,
            JGimpProcedureNotFoundException,
            JGimpInvalidDataCoercionException,
            JGimpIncorrectArgCountException,
            JGimpProcedureExecutionException,
            JGimpException {

        JGimpProcedureDescriptor theProcedure = this.getProcedureDescription(procedureName);
        return callProcedure(theProcedure, commandArgs);
    }

    /**
     * Executes the given procedure, applying the given arguments.
     * @see GimpApp#callProcedure(String, Object[])
     * @throws JGimpProxyException
     * @throws JGimpInvalidDataCoercionException
     * @throws JGimpIncorrectArgCountException
     * @throws JGimpProcedureExecutionException
     */
    public JGimpData[] callProcedure(JGimpProcedureDescriptor procedure, Object[] commandArgs)
        throws JGimpProxyException, JGimpInvalidDataCoercionException, JGimpIncorrectArgCountException, JGimpProcedureExecutionException {

        // get the param types
        JGimpParamDescriptor[] paramTypes = procedure.getParameterDescriptions();

        if (paramTypes.length != commandArgs.length) {
            throw new JGimpIncorrectArgCountException(procedure.getName(), paramTypes.length, commandArgs.length);
        }

        JGimpData[] args = new JGimpData[paramTypes.length];

        // convert args to JGimpData types and add to our list
        for (int i = 0; i < args.length; i++) {
            args[i] = convertJavaObjectToJGimpData(paramTypes[i].getGimpPDBArgType(), commandArgs[i]);
        }
        
        args = addArrayLengths(args);

        // run the procedure, check the return value
        JGimpData[] returnVals = null;
        returnVals = m_Proxy.runPDBProcedure(procedure.getName(), args);
        if (returnVals == null) {
            throw new JGimpProcedureExecutionException(procedure.getName(), "Null return values. (Internal error, shouldn't happen)");
        }
        if (returnVals.length < 1) {
            throw new JGimpProcedureExecutionException(
                procedure.getName(),
                "Return values array is zero. Should have array length of at least 1 to get status back. (Internal error, shouldn't happen)");
        }
        if (returnVals[0] instanceof JGimpPDBStatus) {
            JGimpPDBStatus theStatus = (JGimpPDBStatus) returnVals[0];
            if (theStatus.getStatus() != JGimpPDBStatus.Type.GIMP_PDB_SUCCESS) {
                throw new JGimpProcedureExecutionException(procedure.getName(), "Non-success return status: " + theStatus.getStatus());
            }
        } else {
            System.err.println("Not a status value returned");
            throw new JGimpProcedureExecutionException(
                procedure.getName(),
                "Return value not a status object. Object: "
                    + returnVals[0]
                    + ", class: "
                    + returnVals[0].getClass()
                    + ". (Internal error, shouldn't happen.)");
        }

        // Strip the PDB status object and array counts
        return removeArrayCountsAndPDBStatus(returnVals);
    }
    
    private static JGimpData[] addArrayLengths(JGimpData[] data) throws JGimpInvalidDataCoercionException {
        int numArrays = 0;
        for (int i = 0; i < data.length; i++) {
            if (JGimpPDBArgTypeConstants.isArrayType(data[i].getGimpPDBArgType())) {
                numArrays++;
            }
        }
        if (numArrays < 1) {
            return data;
        }
        JGimpData[] returnArray = new JGimpData[data.length + numArrays];
        for (int returnArrayIndex = 0, dataIndex = 0; dataIndex < data.length; dataIndex++, returnArrayIndex++) {
            JGimpData thisData = data[dataIndex];
            int thisDataType = thisData.getGimpPDBArgType();
            if (JGimpPDBArgTypeConstants.isArrayType(thisDataType)) {
                int arrayLength = -1;
                if (thisDataType == JGimpPDBArgTypeConstants.PDB_FLOATARRAY) {
                    arrayLength = thisData.convertToDoubleArray().length;
                } else if (thisDataType == JGimpPDBArgTypeConstants.PDB_INT16ARRAY) {
                    arrayLength = thisData.convertToShortArray().length;
                } else if (thisDataType == JGimpPDBArgTypeConstants.PDB_INT32ARRAY) {
                    arrayLength = thisData.convertToIntArray().length;
                } else if (thisDataType == JGimpPDBArgTypeConstants.PDB_INT8ARRAY) {
                    arrayLength = thisData.convertToByteArray().length;
                } else { // if (thisDataType == JGimpPDBArgTypeConstants.PDB_STRINGARRAY) {
                    arrayLength = thisData.convertToStringArray().length;
                }
                returnArray[returnArrayIndex] = new JGimpInt32(arrayLength);
                returnArrayIndex++;
            }
            returnArray[returnArrayIndex] = thisData;
        }
        return returnArray;
    }
    
    private static JGimpData[] removeArrayCountsAndPDBStatus(JGimpData[] data) {
        int numArrays = 0;
        for (int i = 1; i < data.length; i++) {
            if (JGimpPDBArgTypeConstants.isArrayType(data[i].getGimpPDBArgType())) {
                numArrays++;
            }
        }
        
        JGimpData[] returnArray = null;
        if (numArrays < 1) {
            returnArray = new JGimpData[data.length-1];
            System.arraycopy(data, 1, returnArray, 0, returnArray.length);
        } else {
            returnArray = new JGimpData[data.length - numArrays - 1];
            for (int returnArrayIndex = 0, dataIndex = 1; dataIndex < data.length; dataIndex++) {
                if ((dataIndex+1) < data.length) {
                    if (JGimpPDBArgTypeConstants.isArrayType(data[dataIndex+1].getGimpPDBArgType())) {
                        continue;
                    }
                }
                returnArray[returnArrayIndex] = data[dataIndex];
                returnArrayIndex++;
            }
        }
        return returnArray;
    }

    /**
     * Attempts to coerce a Java native type to the desired JGimpData type.
     * The desiredType is one of JGimpPDBArgTypeConstants, the data a Java object
     * of some sort. The following algorithm is applied to coerce data types:
     * <ol>
     * <li>If the data passed is an instance of the class it is to be coerced to,
     *     it simply returns the data and performs no processing on it
     * <li>If the type to convert to is one of
     *     <ul>
     *     <li>JGimpPDBArgTypeConstants.PDB_BOUNDARY
     *     <li>JGimpPDBArgTypeConstants.PDB_CHANNEL
     *     <li>JGimpPDBArgTypeConstants.PDB_DISPLAY
     *     <li>JGimpPDBArgTypeConstants.PDB_IMAGE
     *     <li>JGimpPDBArgTypeConstants.PDB_INT32
     *     <li>JGimpPDBArgTypeConstants.PDB_LAYER
     *     <li>JGimpPDBArgTypeConstants.PDB_PATH
     *     <li>JGimpPDBArgTypeConstants.PDB_SELECTION
     *     <li>JGimpPDBArgTypeConstants.PDB_STATUS
     *     </ul>
     *     then the method tries to coerce the data to an integer, then
     *     create a new object of the desired type using that int. The
     *     following is tried to parse the int:
     *     <ol>
     *     <li>The algorithm checks if the data is an instance of Short,
     *         Integer, JGimpInt16, or JGimpInt32. If so, it converts
     *         that object to an int
     *     <li>The algorithm converts the data to a String, then calls "Integer.parseInt()"
     *         on the String
     *     </ol>
     * <li>If the type to convert is a wrapper for a native type (JGimpInt8, JGimpInt16, etc.)
     *     then the algorithm checks if the object is of a Java wrapper type (Byte, Short, etc.)
     *     that is of the same or lesser precision. Thus, a JGimpFloat will check if the Java
     *     object is a Double, Float, Long, Int, Short, etc. If it is any of these, then
     *     it converts the data. Fail that, it turns the object to a String and tries to
     *     parse it using the appropriate Type.parseType() method (e.g., Double.parseDouble())
     * <li>To convert to a PDB_DRAWABLE, it also checks if the object is a JGimpLayer or JGimpChannel.
     *     If so, it will coerce it to a drawable
     * <li>Valid values to convert to PDB_COLOR are: java.awt.Color, or an int or short array of at least
     *     length 3, corresponding to red, green, blue (any values past the first three values are
     *     ignored). Save all this, it attempts to parse out a single int, which is used as all
     *     three RGB values
     * <li>PDB_REGIONS accept Rectangles, or int or short arrays of at least length 4 (values past
     *     the 4th index are ignored). Save this, it attempts to parse out a single int, which is
     *     used as all 4 values
     * <li>All arrays first check if the data is an array of the same or lesser precision of the
     *     desired type. Save that, they test if the object is an object of the same type, or
     *     of lesser precision, as the target array. For example, a PDB_INT32ARRAY will check
     *     if the data is an int[] array, a short[] array, an Integer, or a Short. If it
     *     finds a single value, it creates an array of length one set to that object
     *     the array. Fail all that, it attempts to calls Type.parseType() to see if it
     *     can parse a single value for the array. For example, for the PDB_INT32ARRAY,
     *     if all else fails, it will call Integer.parseInt(data.toString()) to see if it
     *     can parse a single value for the array
     * <li>If the data cannot be converted using any of these methods, an exception is thrown.
     * </ol>
     */
    JGimpData convertJavaObjectToJGimpData(int desiredType, Object data) throws JGimpInvalidDataCoercionException {
        switch (desiredType) {
            case JGimpPDBArgTypeConstants.PDB_BOUNDARY :
                if (data instanceof JGimpBoundary) {
                    return (JGimpBoundary) data;
                }
                try {
                    return new JGimpBoundary(attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_CHANNEL :
                if (data instanceof JGimpChannel) {
                    return (JGimpChannel) data;
                }
                try {
                    return new JGimpChannel(this, attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_COLOR :
                if (data instanceof JGimpColor) {
                    return (JGimpColor) data;
                }
                if (data instanceof Color) {
                    return new JGimpColor((Color) data);
                }
                if (data instanceof int[]) {
                    int[] thisArray = (int[]) data;
                    return new JGimpColor(thisArray[0], thisArray[1], thisArray[2]);
                }
                if (data instanceof short[]) {
                    short[] thisArray = (short[]) data;
                    return new JGimpColor(thisArray[0], thisArray[1], thisArray[2]);
                }
                if (data instanceof byte[]) {
                    byte[] thisArray = (byte[]) data;
                    return new JGimpColor(thisArray[0], thisArray[1], thisArray[2]);
                }
                try {
                    int singleColorValue = attemptIntParse(data);
                    return new JGimpColor(singleColorValue, singleColorValue, singleColorValue);
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_DISPLAY :
                if (data instanceof JGimpDisplay) {
                    return (JGimpDisplay) data;
                }
                try {
                    return new JGimpDisplay(this.getProxy(), attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_DRAWABLE :
                if (data instanceof JGimpDrawable) {
                    // Make a new drawable, because otherwise its getPDBArgType() method may return a layer or channel instead
                    return new JGimpDrawable(this, ((JGimpDrawable)data).getID());
                }
                try {
                    return new JGimpDrawable(this, attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_END :
                return new JGimpEnd();

            case JGimpPDBArgTypeConstants.PDB_FLOAT :
                if (data instanceof JGimpFloat) {
                    return (JGimpFloat) data;
                }
                try {
                    return new JGimpFloat(attemptDoubleParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_FLOATARRAY :
                if (data instanceof JGimpFloatArray) {
                    return (JGimpFloatArray) data;
                }
                try {
                    return new JGimpFloatArray(attemptDoubleArrayParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_IMAGE :
                if (data instanceof JGimpImage) {
                    return (JGimpImage) data;
                }
                try {
                    return new JGimpImage(this, attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_INT16 :
                if (data instanceof JGimpInt16) {
                    return (JGimpInt16) data;
                }
                try {
                    return new JGimpInt16(attemptShortParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_INT16ARRAY :
                if (data instanceof JGimpInt16Array) {
                    return (JGimpInt16Array) data;
                }
                try {
                    return new JGimpInt16Array(attemptShortArrayParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_INT32 :
                if (data instanceof JGimpInt32) {
                    return (JGimpInt32) data;
                }
                try {
                    return new JGimpInt32(attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_INT32ARRAY :
                if (data instanceof JGimpInt32Array) {
                    return (JGimpInt32Array) data;
                }
                try {
                    return new JGimpInt32Array(attemptIntArrayParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_INT8 :
                if (data instanceof JGimpInt8) {
                    return (JGimpInt8) data;
                }
                try {
                    return new JGimpInt8(attemptByteParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_INT8ARRAY :
                if (data instanceof JGimpInt8Array) {
                    return (JGimpInt8Array) data;
                }
                try {
                    return new JGimpInt8Array(attemptByteArrayParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_LAYER :
                if (data instanceof JGimpLayer) {
                    return (JGimpLayer) data;
                }
                try {
                    return new JGimpLayer(this, attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_PARASITE :
                if (data instanceof JGimpParasite) {
                    return (JGimpParasite) data;
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_PATH :
                if (data instanceof JGimpPath) {
                    return (JGimpPath) data;
                }
                try {
                    return new JGimpPath(attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_REGION :
                if (data instanceof JGimpRegion) {
                    return (JGimpRegion) data;
                }
                if (data instanceof Rectangle) {
                    return new JGimpRegion((Rectangle) data);
                }
                if (data instanceof int[]) {
                    int[] thisArray = (int[]) data;
                    return new JGimpRegion(thisArray[0], thisArray[1], thisArray[2], thisArray[3]);
                }
                if (data instanceof short[]) {
                    short[] thisArray = (short[]) data;
                    return new JGimpRegion(thisArray[0], thisArray[1], thisArray[2], thisArray[3]);
                }
                if (data instanceof byte[]) {
                    byte[] thisArray = (byte[]) data;
                    return new JGimpRegion(thisArray[0], thisArray[1], thisArray[2], thisArray[3]);
                }
                try {
                    int singleValue = attemptIntParse(data);
                    return new JGimpRegion(singleValue, singleValue, singleValue, singleValue);
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_SELECTION :
                if (data instanceof JGimpSelection) {
                    return (JGimpSelection) data;
                }
                try {
                    return new JGimpSelection(attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_STATUS :
                if (data instanceof JGimpPDBStatus) {
                    return (JGimpPDBStatus) data;
                }
                try {
                    return new JGimpPDBStatus(attemptIntParse(data));
                } catch (Exception ignoredException) {
                    ; // ignored
                }
                break;

            case JGimpPDBArgTypeConstants.PDB_STRING :
                if (data instanceof JGimpString) {
                    return (JGimpString) data;
                }
                if (data instanceof byte[]) {
                    return new JGimpString(new String((byte[]) data));
                }
                return new JGimpString(data.toString());

            case JGimpPDBArgTypeConstants.PDB_STRINGARRAY :
                if (data instanceof JGimpStringArray) {
                    return (JGimpStringArray) data;
                }
                if (data instanceof String[]) {
                    return new JGimpStringArray((String[]) data);
                }
                return new JGimpStringArray(new String[] { data.toString()});
        }
        throw new JGimpInvalidDataCoercionException(desiredType, data);
    }
    private static byte attemptByteParse(Object data) throws JGimpInvalidDataCoercionException {
        if (data instanceof Byte) {
            return ((Byte) data).byteValue();
        }
        if (data instanceof Boolean) {
            if (((Boolean) data).booleanValue()) {
                return 1;
            }
            return 0;
        }
        if (data instanceof JGimpData) {
            return ((JGimpData) data).convertToByte();
        }
        return Byte.parseByte(data.toString());
    }
    private static byte[] attemptByteArrayParse(Object data) throws JGimpInvalidDataCoercionException {
        if (data instanceof byte[]) {
            return (byte[]) data;
        }
        if (data instanceof JGimpData) {
            return ((JGimpData) data).convertToByteArray();
        }
        return new byte[] { attemptByteParse(data)};
    }
    private static short attemptShortParse(Object data) throws JGimpInvalidDataCoercionException {
        if (data instanceof Boolean) {
            if (((Boolean) data).booleanValue()) {
                return 1;
            }
            return 0;
        }
        if (data instanceof Byte) {
            return ((Byte) data).shortValue();
        }
        if (data instanceof Short) {
            return ((Short) data).shortValue();
        }
        if (data instanceof JGimpData) {
            return ((JGimpData) data).convertToShort();
        }
        return Short.parseShort(data.toString());
    }
    private static short[] attemptShortArrayParse(Object data) throws JGimpInvalidDataCoercionException {
        if (data instanceof short[]) {
            return (short[]) data;
        }
        if (data instanceof byte[]) {
            short[] returnArray = new short[((byte[]) data).length];
            for (int i = 0; i < returnArray.length; i++) {
                returnArray[i] = ((byte[]) data)[i];
            }
            return returnArray;
        }
        if (data instanceof JGimpData) {
            return ((JGimpData) data).convertToShortArray();
        }
        return new short[] { attemptShortParse(data)};
    }
    private static int attemptIntParse(Object data) throws JGimpInvalidDataCoercionException {
        if (data instanceof Boolean) {
            if (((Boolean) data).booleanValue()) {
                return 1;
            }
            return 0;
        }
        if (data instanceof Byte) {
            return ((Byte) data).intValue();
        }
        if (data instanceof Short) {
            return ((Short) data).intValue();
        }
        if (data instanceof Integer) {
            return ((Integer) data).intValue();
        }
        if (data instanceof JGimpData) {
            return ((JGimpData) data).convertToInt();
        }
        return Integer.parseInt(data.toString());
    }
    private static int[] attemptIntArrayParse(Object data) throws JGimpInvalidDataCoercionException {
        if (data instanceof int[]) {
            return (int[]) data;
        }
        if (data instanceof short[]) {
            int[] returnArray = new int[((short[]) data).length];
            for (int i = 0; i < returnArray.length; i++) {
                returnArray[i] = ((short[]) data)[i];
            }
            return returnArray;
        }
        if (data instanceof byte[]) {
            int[] returnArray = new int[((byte[]) data).length];
            for (int i = 0; i < returnArray.length; i++) {
                returnArray[i] = ((byte[]) data)[i];
            }
            return returnArray;
        }
        if (data instanceof JGimpData) {
            return ((JGimpData) data).convertToIntArray();
        }
        return new int[] { attemptIntParse(data)};
    }
    private static double attemptDoubleParse(Object data) throws JGimpInvalidDataCoercionException {
        if (data instanceof Float) {
            return ((Float) data).floatValue();
        }
        if (data instanceof Double) {
            return ((Double) data).doubleValue();
        }
        if (data instanceof Boolean) {
            if (((Boolean) data).booleanValue()) {
                return 1;
            }
            return 0;
        }
        if (data instanceof Byte) {
            return ((Byte) data).doubleValue();
        }
        if (data instanceof Short) {
            return ((Short) data).doubleValue();
        }
        if (data instanceof Integer) {
            return ((Integer) data).doubleValue();
        }
        if (data instanceof Long) {
            return ((Long) data).doubleValue();
        }
        if (data instanceof JGimpData) {
            return ((JGimpData) data).convertToDouble();
        }
        return Double.parseDouble(data.toString());
    }
    private static double[] attemptDoubleArrayParse(Object data) throws JGimpInvalidDataCoercionException {
        if (data instanceof double[]) {
            return (double[]) data;
        }
        if (data instanceof float[]) {
            double[] returnArray = new double[((float[]) data).length];
            for (int i = 0; i < returnArray.length; i++) {
                returnArray[i] = ((float[]) data)[i];
            }
            return returnArray;
        }
        if (data instanceof byte[]) {
            double[] returnArray = new double[((byte[]) data).length];
            for (int i = 0; i < returnArray.length; i++) {
                returnArray[i] = ((byte[]) data)[i];
            }
            return returnArray;
        }
        if (data instanceof short[]) {
            double[] returnArray = new double[((short[]) data).length];
            for (int i = 0; i < returnArray.length; i++) {
                returnArray[i] = ((short[]) data)[i];
            }
            return returnArray;
        }
        if (data instanceof int[]) {
            double[] returnArray = new double[((int[]) data).length];
            for (int i = 0; i < returnArray.length; i++) {
                returnArray[i] = ((int[]) data)[i];
            }
            return returnArray;
        }
        if (data instanceof JGimpData) {
            return ((JGimpData) data).convertToDoubleArray();
        }
        return new double[] { attemptDoubleParse(data)};
    }

    /**
     * Installs the plug-in at the given URL.
     *
     * @throws JGimpProxyException
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @see JGimpProxy#installPlugIn(java.net.URL)
     */
    public void installPlugIn(URL inPlugInURL)
        throws
            JGimpProxyException,
            IOException,
            ClassNotFoundException,
            NoSuchMethodException,
            InstantiationException,
            IllegalAccessException,
            InvocationTargetException {
        m_Proxy.installPlugIn(inPlugInURL);
    }

    /**
     * Install the given plug-in.
     * @throws JGimpProxyException
     * @see JGimpProxy#installPlugIn(JGimpPlugIn)
     */
    public void installPlugIn(JGimpPlugIn inPlugIn) throws JGimpProxyException {
        m_Proxy.installPlugIn(inPlugIn);
    }

    /**
     * Installs the given extension.
     * @throws JGimpProxyException
     * @see JGimpProxy#loadExtension(JGimpExtension)
     */
    public void loadExtension(JGimpExtension inExtension) throws JGimpProxyException {
        m_Proxy.loadExtension(inExtension);
    }

    /**
     * Installs the extension at the given URL.
     * @throws JGimpProxyException
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @see JGimpProxy#loadExtension(URL)
     */
    public void loadExtension(URL inExtensionURL)
        throws
            JGimpProxyException,
            IOException,
            ClassNotFoundException,
            NoSuchMethodException,
            InstantiationException,
            IllegalAccessException,
            InvocationTargetException {
        m_Proxy.loadExtension(inExtensionURL);
    }

}
