///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: JavaApplicationCall.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 1997-2004 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////

package com.ibm.as400.access;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * <P>
 * Provides an easy way to run Java
 * applications on the IBM i system's Java Virtual Machine from a client.
 * The client Java program specifies the environment, the program to run,
 * and the program parameters.  The program then runs on the IBM i system's
 * Java Virtual Machine.  Text-based input/output is provided by
 * JavaApplicationCall.  Input can be sent to the Java program which
 * will receive the input via standard input (<b><tt>stdin</tt></b>).
 * Standard output (<b><tt>stdout</tt></b>) and
 * standard error (<b><tt>stderr</tt></b>) text generated by the Java program are received
 * by JavaApplicationCall and made available to the calling program.
 * JavaApplicationCall does not support displaying the graphical user
 * interface of the IBM i system's Java program on the client.  Other Java
 * facilities, such as remote AWT must be used to display graphical
 * interfaces on the client.
 *
 * <P>
 * Sockets are used to send stdin, stdout and stderr between
 * client and IBM i system.  The port used can be set via setPort().  The
 * default port sequence is 2850, 2851 and 2852.  If the port is in use,
 * this class searches for available ports if findPort is true.
 * stdin, stdout and stderr are <i>not</i> transported
 * across a secure connection even when the rest of the Toolbox
 * is using SSL.
 *
 * <P>
 * Note: The presence of a firewall, in either direction between the IBM i system
 * and the client, may prevent JavaApplicationCall from
 * opening the necessary additional ports (for stdin, stdout, and stderr).
 * The administrator may need to authenticate the application through
 * the firewall in <i>both</i> directions:
 * From client to IBM i system; and from IBM i system to client.
 *
 * <P>
 * For example, suppose the Java class <tt>HelloWorld</tt> resides in directory
 * <tt>/javatest</tt> on the system.  The following calls this program and
 * receives program output written to stdout and stderr.
 *
 * <a name="ex"> </a>
 * <pre>
 * import com.ibm.as400.access.*;
 *
 * public class test implements Runnable
 * {
 *    JavaApplicationCall jaCall;
 *
 *    public static void main(String[] args)
 *    {
 *       test me = new test();
 *       me.Main(args);
 *    }
 *
 *
 *    void Main(String[] args)
 *    {
 *
 *       try
 *       {
 *           // The IBM i system where the Java program to run, is located.
 *           AS400 as400 = new AS400();
 *
 *           // Construct a JavaApplicationCall object.
 *           jaCall = new JavaApplicationCall(as400);
 *
 *           // Set the name of the Java application to be run.
 *           jaCall.setJavaApplication("HelloWorld");
 *
 *           // Set the classpath environment variable used by the IBM i system's
 *           // JVM so it can find the class to run.
 *           jaCall.setClassPath("/javatest");
 *
 *           // Start the thread that will receive stdout
 *           Thread outputThread = new Thread(this);
 *           outputThread.start();
 *
 *           // Start the program.  The call to run() will not return
 *           // until the IBM i system Java program completes.  If the Toolbox
 *           // cannot start the Java program, false is returned with
 *           // a list of AS400Message objects indicating why the program
 *           // could not start.
 *           if (jaCall.run() != true)
 *           {
 *                AS400Message[] messageList = jaCall.getMessageList();
 *                for (int msg = 0; msg < messageList.length; msg++)
 *                    System.out.println(messageList[msg].toString());
 *           }
 *       }
 *       catch (Exception e) { e.printStackTrace(); }
 *
 *       System.exit(0);
 *    }
 *
 *    // This thread will get the output from the IBM i system Java program,
 *    // and print it.  Note the call to sleep().
 *    // The getXxxString() methods return immediately even if there is no data.
 *    public void run()
 *    {
 *        while (true)
 *        {
 *           String stdOut = jaCall.getStandardOutString();
 *           if (stdOut != null)
 *             System.out.println(stdOut);
 *
 *           String stdErr = jaCall.getStandardErrorString();
 *           if (stdErr != null)
 *             System.out.println(stdErr);
 *
 *           try { Thread.sleep(100); } catch (Exception e) {}
 *        }
 *    }
 * }
 *
 *
 * </pre>
 *
**/

public class JavaApplicationCall implements Serializable
{
    static final long serialVersionUID = 4L;

    // The variables represent the default stdin, stdout, and stderr port.
    private static int DEFAULT_STANDARD_ERROR_PORT = 2852;
    private static int DEFAULT_STANDARD_IN_PORT    = 2850;
    private static int DEFAULT_STANDARD_OUT_PORT   = 2851;
    private static int PORT_SEARCH_TIMES           = 1000;

    private AS400 as400_ = null;
    private CommandCall command_ = null;
    private AS400Message[] messageList_ = null;

    // lists of listeners and event catchers
    private transient PropertyChangeSupport propertyChange_ = new PropertyChangeSupport(this);
    private transient VetoableChangeSupport vetoableChange_ = new VetoableChangeSupport(this);
    private transient Vector completedListeners_ = new Vector();

    private transient JavaApplicationCallThread openSocketsThread_ = null;

    // The variables represent connections built in the subsidiary threads.
    private transient ServerSocket error_ = null;
    private transient ServerSocket read_  = null;
    private transient ServerSocket write_ = null;

    // The variables represent the properties of JavaApplicationCall.
    private String classPath_ = "";
    private int    defaultPort_ = DEFAULT_STANDARD_IN_PORT;
    private boolean findPort_ = true;
    private int    garbageCollectionInitialSize_ = 2048;
    private String garbageCollectionMaximumSize_ = "*NOMAX";
    private int    garbageCollectionFrequency_ = 50;
    private int    garbageCollectionPriority_ = 20;
    private String interpret_ = "*OPTIMIZE";
    private String jobname_ = "QJVACMDSRV"; // @F0A
    private String javaApplication_ = null;
    private String optimization_ = "10";
    private String[] options_ = {"*NONE"};
    private String[] parameters_ = {};
    private Properties properties_ = null;
    private String securityCheckLevel_ = "*WARN";
    private int standardErrorPort_ = DEFAULT_STANDARD_ERROR_PORT;
    private int standardInPort_ = DEFAULT_STANDARD_IN_PORT;
    private int standardOutPort_ = DEFAULT_STANDARD_OUT_PORT;


    /**
     * Constructs a JavaApplicationCall object.
    **/
    public JavaApplicationCall()
    {
    }



    /**
     * Constructs a JavaApplicationCall object. The Java program
     * is on an IBM i <i>system</i>.
     *
     * @param system The system on which contains the Java program.
    **/
    public JavaApplicationCall(AS400 system)
    {
        if (system == null)
            throw new NullPointerException("system");

        as400_ = system;
    }



    /**
     * Constructs a JavaApplicationCall object. The Java program is namee
     * <i>application</i> and runs on system <i>system</i>.
     *
     * @param system The system on which contains the Java program.
     * @param application  The name of Java program.
    **/
    public JavaApplicationCall(AS400 system,  String application)
    {
        this(system);

        if (application == null)
            throw new NullPointerException("application");

        javaApplication_ = application;
    }



    /**
     * Constructs a JavaApplicationCall object. The Java program is named
     * <i>application</i> and runs on system <i>system</i>.  <i>
     * classPath</i> is passed to the system as the value of the
     * CLASSPATH environment variable.
     *
     * @param system The system on which contains the Java program.
     * @param application  The name of Java program.
     * @param classPath The value of the environment variable CLASSPATH.
    **/
    public JavaApplicationCall(AS400 system,  String application,  String classPath)
    {
        this(system, application);

        if (classPath == null)
            throw new NullPointerException("classPath");

        this.classPath_ = classPath;
    }



    /**
     * Adds an ActionCompletedListener to be notified when the Java application ends.
     * The specified ActionCompletedListeners <b>actionCompleted</b> method will
     * be called each time an application runs.
     *
     * @see #removeActionCompletedListener
     * @param listener The ActionCompletedListener.
     */
    public void addActionCompletedListener(ActionCompletedListener listener)
    {
        if (listener != null)
            completedListeners_.addElement(listener);
        else
            throw new NullPointerException("addActionCompletedListener");
    }



    /**
     * Adds a listener to be notified when the value of any bound property
     * changes.
     *
     * @see #removePropertyChangeListener
     * @param  listener  The listener.
    **/
    public void addPropertyChangeListener(PropertyChangeListener listener )
    {
        if (listener != null)
            propertyChange_.addPropertyChangeListener(listener);
        else
            throw new NullPointerException("addPropertyChangeListener");
    }



    /**
     * Adds a listener to be notified when the value of any constrained
     * property changes.
     *
     * @see #removeVetoableChangeListener
     * @param  listener  The listener.
    **/
    public void addVetoableChangeListener(VetoableChangeListener listener )
    {
        if (listener != null)
            vetoableChange_.addVetoableChangeListener(listener);
        else
            throw new NullPointerException("addVetoableChangeListener");
    }



    /**
     * Returns the value of the CLASSPATH environment variable when running
     * the Java program.  Use the forward slash to separate path elements
     * and a colon to separate the elements of CLASSPATH.  For example,
     * /dir1:/dir1/dir2/myClasses.jar.
     * <p>Valid values are:
     * <UL>
     * <li>*ENVVAR No setting is sent from the client to the IBM i system so the
     *      IBM i system's default value of CLASSPATH is used.
     *  <li>string The value that is set before the program is run.
     *  </UL>
     *
     * @return The value of CLASSPATH.
    **/
    public String getClassPath()
    {
        return classPath_;
    }



    /**
     Returns the CommandCall object that was used in the most recent invocation of {@link #run() run()}.
     @return The CommandCall object; null if run() has not been called.
     **/
    public CommandCall getCommandCall()
    {
      return command_;
    }




    /**
     * Returns the default port used to transfer stdin, stdout,
     * and stderr between the client and the IBM i system.  Three
     * ports are used.  The port returned by this method is used for
     * stdin; (port + 1) is used for stdout; and (port + 2)
     * is used for stderr.  If not otherwise specified, the default port is 2850.
     *
     * @return The default port.
    **/
    public int getDefaultPort()
    {
        return defaultPort_;
    }




    /**
       Gets stdin, stdout and stderr port.
       Also creates serversockets for stdin, stdout and stderr.
    **/
    private void getFreePort()
    {
        read_ = null;
        write_ = null;
        error_ = null;

        if (!isFindPort())
        {
            standardInPort_ = defaultPort_;
            standardOutPort_ = defaultPort_ + 1;
            standardErrorPort_ = defaultPort_ + 2;
            try
            {
                read_ = new ServerSocket(standardInPort_);
            }
            catch (Exception e)
            {
                Trace.log(Trace.ERROR, e.toString(), e);
            }
            try
            {
                write_ = new ServerSocket(standardOutPort_);
            }
            catch (Exception e)
            {
                Trace.log(Trace.ERROR, e.toString(), e);
            }
            try
            {
                error_ = new ServerSocket(standardErrorPort_);
            }
            catch (Exception e)
            {
                Trace.log(Trace.ERROR, e.toString(), e);
            }
        }
        else
        {
            standardInPort_ = searchPort(defaultPort_ , DEFAULT_STANDARD_IN_PORT);
            standardOutPort_ = searchPort(standardInPort_+1, DEFAULT_STANDARD_OUT_PORT);
            standardErrorPort_ = searchPort(standardOutPort_+1, DEFAULT_STANDARD_ERROR_PORT);
        }

        if (read_ == null || write_ == null || error_ == null)
        {
            try
            {
                if (read_ != null) read_.close();
                read_ = null;
            }
            catch(Exception e){}
            try
            {
                if (write_ != null) write_.close();
                write_ = null;
            }
            catch(Exception e){}
            try
            {
                if (error_ != null) error_.close();
                error_ = null;
            }
            catch(Exception e){}

            String msg = "Setup of connection with IBM i system failed.";
            Trace.log(Trace.ERROR, msg);
            throw new RuntimeException(msg);
        }
    }

    /**
     * Returns the relative frequency that garbage collection runs.  This
     * value applies only to OS/400 V4R2 and V4R3.  It is
     * ignored in V4R4 and later versions.
     *
     * @return The relative frequency.
    **/
    public int getGarbageCollectionFrequency()
    {
       return garbageCollectionFrequency_;
    }



    /**
     * Returns the initial size, in kilobytes, of the garbage collection heap.
     * A large size can keep the garbage collector from starting when
     * the Java program is small, improving performance.
     *
     * <p>Possible values are:
     * <UL>
     * <li>2048 The default initial size is 2048 kilobytes.
     *
     * <li>heapSize  The initial value of the garbage collection heap in
     *               kilobytes. We recommend the initial heap size be
     *               2048 kilobytes (the default) or larger.
     * </UL>
     * @return  The initial size of the garbage collection heap.
    **/
    public int getGarbageCollectionInitialSize()
    {
        return garbageCollectionInitialSize_;
    }




    /**
     * Returns the maximum size, in kilobytes, that the garbage
     * collection heap can grow to.  This value is used to
     * prevent runaway programs from consuming all available
     * storage.
     *
     * <p>Possible values are:
     * <UL>
     * <li>*NOMAX The default maximum size is not specified by the user.  The
     *            maximum is determined by the system.  The heap will grow until
     *            all system resources are depleted.  Then a synchronous garbage
     *            collection is started to reclaim resources no longer in use.
     *
     * <li>heapSize The value in kilobytes that the garbage collection
     *              heap can grow to.
     * </UL>
     * @return The maximum size that the garbage collection heap can grow to.
    **/
    public String getGarbageCollectionMaximumSize()
    {
        return garbageCollectionMaximumSize_;
    }



    /**
     * Returns the priority of the tasks running garbage collection.  This
     * value applies only to OS/400 V4R2 and V4R3.  It is
     * ignored in V4R4 and later versions.
     *
     * @return  The priority of the tasks.
    **/
    public int getGarbageCollectionPriority()
    {
        return garbageCollectionPriority_;
    }





    /**
     * Returns whether all Java class files should be run interpretively.
     *
     * <p>Possible values are:
     * <UL>
     * <li>*OPTIMIZE Whether all Java classes are run interpretively depends on the
     *               value of the optimize property.  If optimize is
     *               *INTERPRET, all Java classes will be run
     *               interpretively.  If any other value was specified for
     *               optimize, only Java classes with Java programs created
     *               using CRTJVAPGM command and specifying OPTIMIZE(*INTERPRET) will
     *               be run interpretively.
     *
     * <li>*NO       Only Java classes with Java programs created using CRTJVAPGM
     *               command and specifying OPTIMIZE(*INTERPRET) will be run
     *               interpretively.  Java classes that need a Java program created
     *               will be created with the optimization level specified in the
     *               optimize property.
     *
     * <li>*YES      All Java classes will be run interpretively regardless of the
     *               OPTIMIZE value associated Java program.  Java classes that need
     *               a Java program created will use the optimization level specified
     *               in the optimize property.
     *  </UL>
     *
     * @return  Whether all Java class files should be run interpretively.
    **/
    public String getInterpret()
    {
        return interpret_;
    }



    /**
     * Returns the name of Java application to be run.
     * If the Java application is not be set, null is returned.
     *
     * @return The name of Java application.
    **/
    public String getJavaApplication()
    {
        return javaApplication_;
    }



    /**
     * Returns the name that this job will run under.
     * The maximum length returned is 10 characters.
     *
     * <p>Possible values are:
     * <UL>
     * <li>QJVACMDSRV - The default job name.
     *
     * <li>*GEN       - The job name will be generated from the class name.
     *
     * <li>string     - Any generic string value.
     *  </UL>
     *
     * @return The value of the job name.
     * @since i5/OS V5R3M0
    **/
    public String getJobName() // @F0A
    {
        return jobname_;
    }



    /**
      *Returns the list of IBM i system messages generated if the Java program
      *cannot be started.  Before run() is called and if the Java program
      *can be started, an empty list is returned.
      *
      *@return  The array of AS400Message objects.
      */
    public AS400Message[] getMessageList()
    {
        return messageList_;
    }



    /**
     * Returns the optimization level of IBM i system Java programs that
     * will be created if no Java program is associated with the Java
     * class.
     *
     * *INTERPRET means the resulting Java program interprets the
     * class byte codes when invoked.  For other optimization levels, the
     * Java program contains machine instruction sequences that are run
     * when the Java program is invoked.
     *
     * *INTERPRET Java programs are smaller but run slower than
     * Java programs created with higher optimization levels.  As you
     * increase the optimization level beyond 10, the Java program
     * performance generally improves, but the time required to create the
     * Java program increases, and debugging is more difficult.
     *
     * <p>Possible values are:
     * <UL>
     * <li>10          The Java program contains a compiled version of the class file
     *                 byte codes and has only minimal additional compiler
     *                 optimization.  Variables can be displayed and modified while
     *                 debugging.
     *
     * <li>*INTERPRET  The Java program created does not contain machine specific
     *                 instructions.  It will be interpreted when the program is
     *                 started.  Variables can be displayed and modified while
     *                 debugging.
     *
     *                 If the INTERPRET property has a value of *OPTIMIZE, all of the
     *                 classes that run will be run interpreted even if there is an
     *                 optimized Java program associated with the class.
     *
     * <li>20          The Java program contains a compiled version of the class file
     *                 byte codes and has some additional compiler optimization.
     *                 Variables can be displayed but not modified while debugging.
     *
     * <li>30          The Java program contains a compiled version of the class file
     *                 byte codes and has more compiler optimization than optimization
     *                 level 20.  During a debug session, user variables cannot be
     *                 changed, but can be displayed.  The presented values may not be
     *                 the current values of the variables.
     *
     * <li>40          The Java program contains a compiled version of the class file
     *                 byte codes and has more compiler optimization than optimization
     *                 level 30.  All call and instruction tracing is disabled.
     * </UL>
     * @return     The optimization level of the IBM i system Java application.
    **/
    public String getOptimization()
    {
        return optimization_;
    }

    /**
     * Returns a list of special options used when running the Java class.
     *
     * <p>The possible values are:
     * <UL>
     * <li>*NONE      No special options are used when running the Java class.
     *
     * <li>*VERBOSE   A message is displayed each time a class file is loaded.
     *
     * <li>*DEBUG     Allows the system debugger to be used for this Java program.
     *
     * <li>*VERBOSEGC A message is displayed for each garbage collection sweep.
     *
     * <li>*NOCLASSGC Unused classes are not reclaimed when garbage collection is run.
     * </UL>
     *
     * @return  The options used when running the Java class.
    **/
    public String[] getOptions()
    {
        return options_;
    }


    /**
     * Returns parameter values that are passed to the Java
     * application.  A maximum of 200 parameter values can be passed.
     *
     * @return The parameters when running the Java application.
    **/
    public String[] getParameters()
    {
        return parameters_;
    }

    /**
     * Returns the properties set on the IBM i system's JVM before running
     * the Java program.
     *
     * @return The properties.
    **/
    public Properties getProperties()
    {
        return properties_;
    }

    /**
     * Returns the level of warnings given for directories in the
     * CLASSPATH that have public write authority.  A directory in the
     * CLASSPATH that has public write authority is a security exposure
     * because it may contain a class file with the same name as the one
     * you want to run. Whichever class file is found first is run.
     *
     * <p>The possible values are:
     * <UL>
     * <li>*WARN Warning message is sent for each directory in the CLASSPATH
     *           that has public write authority.
     *
     * <li>*SECURE  A warning message is sent for each directory in the CLASSPATH
     *              that has public write authority.  If one or more warning
     *              messages are sent, an escape message is sent and the Java
     *              program is not run.
     *
     * <li>*IGNORE  Ignore the fact that directories in the CLASSPATH may have
     *              public write authority.  No warnings messages are sent.
     * </UL>
     * @return  The level of warnings given for directories in the
     *          CLASSPATH that have public write authority.
    **/
    public String getSecurityCheckLevel()
    {
        return securityCheckLevel_;
    }

    /**
     * Returns the stderr port.
     * If not changed, the default port 2852 is returned.
     *
     * @return The stderr port.
    **/
    private int getStandardErrorPort()
    {
        return standardErrorPort_;
    }

    /**
     *  Returns the next string written to stderr by the program
     *  running on the system.
     *
     *  @return the next stderr string from the system.
    **/
    public String getStandardErrorString()
    {
        if (openSocketsThread_ != null)
            return openSocketsThread_.getStandardErrorString();
        else
            return null;
    }

    /**
     * Returns the stdin port.
     * If not set, the default port 2850 is returned.
     *
     * @return The stdin port.
    **/
    private int getStandardInPort()
    {
        return standardInPort_;
    }

    /**
     * Returns the stdout port.
     * If not set, the default port 2851 is returned.
     *
     * @return The stdout port.
    **/
    private int getStandardOutPort()
    {
        return standardOutPort_;
    }

    /**
     *  Returns the next string written to stdout by the application.
     *
     *  @return the string written to stdout.
    **/
    public String getStandardOutString()
    {
        if (openSocketsThread_ != null)
            return openSocketsThread_.getStandardOutString();
        else
            return null;
    }

    /**
       Returns the system which contains the Java program.

       @return The system.
    **/
    public AS400 getSystem()
    {
        return as400_;
    }

    /**
       Indicates if this class should search for a free port.

       @return true if this class will search for a free port; false otherwise.
       The default value is <tt>true</tt>.
    **/
    public boolean isFindPort()
    {
        return findPort_;
    }

    /**
       Deserializes and initializes the transient data.
    **/
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
    {
        in.defaultReadObject();

        propertyChange_ = new PropertyChangeSupport(this);
        vetoableChange_ = new VetoableChangeSupport(this);
        completedListeners_ = new Vector();
        openSocketsThread_ = null;
        read_ = null;
        write_ = null;
        error_ = null;
    }

    /**
     * Removes this ActionCompletedListener from the list of listeners.
     * If the ActionCompletedListener is not on the list, nothing is done.
     *
     * @see #addActionCompletedListener
     * @param listener The ActionCompletedListener.
     */
    public synchronized void removeActionCompletedListener(ActionCompletedListener listener)
    {
        if (listener != null)
            completedListeners_.removeElement(listener);
        else
            throw new NullPointerException("removeActionCompletedListener");
    }

    /**
     * Removes a property change listener from the list of listeners.
     *
     * @see #addPropertyChangeListener
     * @param listener The listener.
    **/
    public void removePropertyChangeListener(PropertyChangeListener listener )
    {
        if (listener != null)
            propertyChange_.removePropertyChangeListener(listener);
        else
            throw new NullPointerException("removePropertyChangeListener");
    }

    /**
     * Removes a vetoable change listener from the list of listeners.
     *
     * @see #addVetoableChangeListener
     * @param listener The listener.
    **/
    public void removeVetoableChangeListener(VetoableChangeListener listener )
    {
        if (listener != null)
            vetoableChange_.removeVetoableChangeListener(listener );
        else
            throw new NullPointerException("removeVetoableChangeListener");
    }

    /**
       Run the Java application.  Control will not be returned to the
       calling application until the program completes.  If the program
       does not start, a list of AS400Message object containing information
       about is failure is available.

       @return true if the program can be started, false otherwise.

       @exception AS400SecurityException If a security or authority error occurs.
       @exception ConnectionDroppedException If the connection is dropped unexpectedly.
       @exception ErrorCompletingRequestException If an error occurs before the request is completed.
       @exception InterruptedException If this thread is interrupted.
      @exception IOException If an error occurs while communicating with the system.
       @exception ServerStartupException If the host server cannot be started.
       @exception UnknownHostException If the system cannot be located.

    **/
    public boolean run() throws AS400SecurityException,
                                ConnectionDroppedException,
                                ErrorCompletingRequestException,
                                InterruptedException,
                                IOException,
                                ServerStartupException,
                                UnknownHostException
    {
        if (getSystem() == null)
            throw new ExtendedIllegalStateException("system",
                 ExtendedIllegalStateException.PROPERTY_NOT_SET);

        if (getJavaApplication() == null)
            throw new ExtendedIllegalStateException("javaApplication",
                 ExtendedIllegalStateException.PROPERTY_NOT_SET);

                              // force signon so sign-on dialog up before
                              // we create more threads.
        getSystem().getVRM();                        // @D1A

        getFreePort();
        openSocketsThread_ = new JavaApplicationCallThread(read_, write_, error_);
        openSocketsThread_.start();

        command_ = new CommandCall(getSystem());
        command_.suggestThreadsafe(false); // JAVA cmd isn't threadsafe.  @A1A

        // Construct commandstring
        // 1.  CLASS
        // 2.  PARM
        // 3.  CLASSPATH
        // 4.  CHKPATH
        // 5.  OPTIMIZE
        // 6.  INTERPRET
        // 7.  PROP
        // 8.  GCHINL
        // 9.  GCHMAX
        // 10. GCFRQ
        // 11. GCPTY
        // 12. OPTION

        // 1.CLASS
        StringBuffer commandString = new StringBuffer("JAVA CLASS(" + getJavaApplication() + ") ");
        // 2.PARM
        String[] parm = getParameters();
        if (parm.length > 0)
        {
            commandString.append("PARM(");
            for (int i = 0 ; i < parm.length; i++) {
                commandString.append("'" + parm[i] + "' ");
            }
            // delete last space
            commandString.delete(commandString.length()-1, commandString.length());
            commandString.append(") ");
        }
        // 3.CLASSPATH
        if (getClassPath().length() > 0)
            commandString.append("CLASSPATH('" + getClassPath() + "') ");
        // 4.CHKPATH
        commandString.append("CHKPATH(" + getSecurityCheckLevel() +") ");
        // 5.OPTIMIZE
        commandString.append("OPTIMIZE(" + getOptimization() + ") ");
        // 6.INTERPRET
        commandString.append("INTERPRET(" + getInterpret() + ") ");
        // 7.PROP

        String localAddress = null;                                         // @E1c
                                                                            // @E1a
        // Because of bugs in Java, cannot get the local                    // @E1a
        // address until a socket (not a server socket)                     // @E1a
        // is connected.                                                    // @E1a
        try                                                                 // @E1a
        {                                                                   // @E1a
           Socket pmSocket = new Socket(getSystem().getSystemName(), 449);  // @E1a
           localAddress = pmSocket.getLocalAddress().getHostAddress();      // @E1a
           pmSocket.close();                                                // @E1a
        }                                                                   // @E1a
        catch (Exception e) { localAddress = null; }                        // @E1a
                                                                            // @E1a
        // if new method didn't work, try the old technique.                // @E1a
        if (localAddress == null)                                           // @E1a
        {
           localAddress = "localhost";

           try
           {
               localAddress = java.net.InetAddress.getLocalHost().getHostAddress();
           }
           catch (java.net.UnknownHostException e)
           {
               Trace.log(Trace.ERROR , e.toString());
           }
        }                                                                   // @E1a

        commandString.append("PROP("
                        +"(os400.stdin 'port:"+localAddress+":"+new Integer(getStandardInPort()).toString()+"') "
                        +"(os400.stdout 'port:"+localAddress+":"+new Integer(getStandardOutPort()).toString()+"') "
                        +"(os400.stderr 'port:"+localAddress+":"+new Integer(getStandardErrorPort()).toString()+"') ");
        commandString.append("(os400.stdio.convert N)");

        if (getProperties() != null)
        {
            Enumeration e = properties_.keys();
            //add all other properties here
            while (e.hasMoreElements()) {
                String key = (String) e.nextElement();
                String value = (String)properties_.get(key);
                commandString.append(" (" + key + " " + value + ")");
            }
        }

        commandString.append(") ");
        // 8.GCHINL
        commandString.append("GCHINL(" + getGarbageCollectionInitialSize() + ") ");
        // 9.GCHMAX
        commandString.append("GCHMAX(" + getGarbageCollectionMaximumSize() + ") ");
        // 10.GCFRQ
        commandString.append("GCFRQ(" + getGarbageCollectionFrequency() + ") ");
        // 11.GCPTY
        commandString.append("GCPTY(" + getGarbageCollectionPriority() + ") ");
        // 12.OPTION
        String[] opt = getOptions();
        if (opt.length > 0)
        {
          commandString.append("OPTION(");
          for (int i = 0 ; i < opt.length; i++) {
            commandString.append(opt[i] + " ");
          }
          // delete last space
          commandString.delete(commandString.length()-1, commandString.length());
          commandString.append(") ");
        }
        // 13.JOB                                            @F0A
        if (as400_.getVRM() >= 0x00050300)  // New in V5R3M0 @F0A
            commandString.append("JOB(" + getJobName() +") "); // @F0A
        //run the command
        boolean success = false;

        // System.out.println(commandString);

        try
        {
           success = command_.run(commandString.toString());
        }
        catch (PropertyVetoException e) {}
        finally
        {
           openSocketsThread_.closeSockets();
           openSocketsThread_ = null;
           parameters_ = new String[0];
           properties_ = null;
           read_ = null;
           write_ = null;
           error_ = null;
        }

        if (success)
        {
            // fire ActionCompletedEvent
            Vector targets;
            targets = (Vector) completedListeners_.clone();
            ActionCompletedEvent actionEvt = new ActionCompletedEvent(this);

            for (int i = 0; i < targets.size(); i++)
            {
                ActionCompletedListener target = (ActionCompletedListener)targets.elementAt(i);
                target.actionCompleted(actionEvt);
            }
        }

        messageList_ = command_.getMessageList();

        return success;
    }


    /**
       Find a free port. If the port passed as a parameter is in use, try port+=3.
       If that is busy keep trying for up to 1000 times.
       If we cannot find a free port, return the original port.
    **/
    private int searchPort(int port, int whichPort)
    {
        ServerSocket test = null;
        int curport = port;
        boolean found = false;
        int times = 0;

        while (times <= PORT_SEARCH_TIMES)
        {
            try
            {
                test = new ServerSocket(curport);
            }
            catch (Exception e)
            {
                curport += 3;
                times++;
                continue;
            }
               found = true;
               break;
        }
        if (found)
        {
            if (whichPort == DEFAULT_STANDARD_IN_PORT)
                read_ = test;
            else if (whichPort == DEFAULT_STANDARD_OUT_PORT)
                write_ = test;
            else
                error_ = test;
        }
        else
            curport = port;

        return curport;
    }

    /**
        Sends the stdin to the application running on the system.

        @param data The stdin to the system.
    **/
    public void sendStandardInString(String data)
    {
        if (openSocketsThread_ != null)
            openSocketsThread_.sendStandardInString(data);
    }

    /** Sets the value of the CLASSPATH environment variable when running
     * the Java program.  Use the forward slash to separate path elements
     * and a colon to separate the elements of CLASSPATH.  For example,
     * /dir1:/dir1/dir2/myClasses.jar.
     * <p>Valid values are:
     * <UL>
     * <li>*ENVVAR No setting is sent from the client to the IBM i system so the
     *      IBM i system's default value of CLASSPATH is used.
     *  <li>string The value that is set before the program is run.
     *  </UL>
     *
     * @param  classPath  The value of the classpath.
     * @exception PropertyVetoException If the change is vetoed.
    **/
    public void setClassPath(String classPath) throws PropertyVetoException
    {
        if (classPath != null)
        {
            String old = this.classPath_;
            vetoableChange_.fireVetoableChange("classPath",old,classPath);
            this.classPath_ = classPath;
            propertyChange_.firePropertyChange("classPath",old,classPath);
        }
        else
            throw new NullPointerException("classPath");
    }

    /**
       Sets the default port.  This is the port for stdin.
       stdout is (port + 1) and stderr is (port + 2).  {@link #setFindPort setFindPort()}
       can be used to tell this class to search for a free port if these
       ports are in use.

       @param port The default port.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setDefaultPort(int port) throws PropertyVetoException
    {
        if (port > 0)
        {
            Integer old = new Integer(this.defaultPort_);
            vetoableChange_.fireVetoableChange("defaultPort", old, new Integer(port));
            this.defaultPort_ = port;
            propertyChange_.firePropertyChange("defaultPort", old, new Integer(port));
        }
        else
            throw new IllegalArgumentException("defaultPort");
    }

    /**
       Sets searching for a free port.

       @param search true to search for a port that is not in use; false to not search.
       The default value is <tt>true</tt>.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setFindPort(boolean search)  throws PropertyVetoException
    {
        Boolean old = new Boolean(findPort_);
        vetoableChange_.fireVetoableChange("findPort",old,new Boolean(search));
        findPort_ = search;
        propertyChange_.firePropertyChange("findPort",old,new Boolean(search));
    }

    /**
       Sets the relative frequency that garbage collection runs.

       This parameter is valid only for OS/400 V4R2 and V4R3 versions.
       It is ignored for V4R4 and later versions.

       @param frequency  The relative frequency that garbage collection runs.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setGarbageCollectionFrequency(int frequency) throws PropertyVetoException
    {
        if ((frequency >= 0) && (frequency <= 100))
        {
            int old = this.garbageCollectionFrequency_;
            vetoableChange_.fireVetoableChange("garbageCollectionFrequency",new Integer(old),new Integer(frequency));
            this.garbageCollectionFrequency_ = frequency;
            propertyChange_.firePropertyChange("garbageCollectionFrequency",new Integer(old),new Integer(frequency));
        }
        else
            throw new IllegalArgumentException("garbageCollectionFrequency");
    }

    /**
       Sets the initial size, in kilobytes, of the garbage collection
       heap.  This is used to prevent garbage collection from starting on
       small programs.

       <p>The possible values are:
       <UL>
       <li>2048 The default initial size is 2048 kilobytes.

       <li>heap-initial-size  The initial value of the garbage collection heap, in
                              kilobytes. We recommend that the initial heap size be
                              set to 2048 kilobytes (the default) or larger.

       </UL>

       @param size  The initial size of the garbage collection heap.
       @exception PropertyVetoException If the change is vetoed.
    **/

    public void setGarbageCollectionInitialSize(int size) throws PropertyVetoException
    {
      if (size > 0)
      {
        if (Trace.traceOn_)
        {
          int sizeValue = -1;
          boolean nomax = false;
          try
          {
            sizeValue = Integer.parseInt(getGarbageCollectionMaximumSize());
          }
          catch(NumberFormatException e)
          {
            nomax = true;
          }

          if (!nomax && size > sizeValue)
          {
            Trace.log(Trace.WARNING , "Initial size ("+size+") exceeds specified maximum size (" + sizeValue + ")");
          }
        }

        int old = this.garbageCollectionInitialSize_;
        vetoableChange_.fireVetoableChange("garbageCollectionInitialSize",new Integer(old),new Integer(size));
        this.garbageCollectionInitialSize_ = size;
        propertyChange_.firePropertyChange("garbageCollectionInitialSize",new Integer(old),new Integer(size));
      }
      else
        throw new IllegalArgumentException("garbageCollectionInitialSize");
    }

    /**
       Sets the maximum size, in kilobytes, that the garbage
       collection heap can grow to.  This is used to prevent runaway
       programs that consume all of the available storage.  Normally,
       garbage collection runs as an asynchronous thread in parallel with
       other threads.  If the maximum size is reached, all other threads
       are stopped while garbage collection takes place.  This may
       adversely affect performance.

       <p>The possible values are:
       <UL>
       <li>*NOMAX           The default maximum size is not specified by the user.  The
                            maximum is determined by the system.  The heap will grow until
                            all system resources are depleted.  Then a synchronous garbage
                            collection is started to reclaim resources no longer in use.

       <li>heap-maximum-size The value in kilobytes that the garbage collection
                             heap can grow to.
       </UL>

       @param size  The maximum size that the garbage collection heap can grow to.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setGarbageCollectionMaximumSize(String size) throws PropertyVetoException
    {
        if (size != null)
        {
            if (!size.toUpperCase().equalsIgnoreCase("*NOMAX"))
            {
                try
                {
                    Integer.parseInt(size);
                }
                catch(NumberFormatException e)
                {
                    throw new IllegalArgumentException("garbageCollectionMaximumSize");
                }
            }

            String old = this.garbageCollectionMaximumSize_;
            vetoableChange_.fireVetoableChange("garbageCollectionMaximumSize",old,size);
            this.garbageCollectionMaximumSize_ = size;
            propertyChange_.firePropertyChange("garbageCollectionMaximumSize",old,size);
        }
        else
            throw new NullPointerException("garbageCollectionMaximumSize");
    }

    /**
       Sets the priority of the tasks running garbage collection.
       This parameter is valid only for OS/400 V4R3 and V4R2 versions.
       It is ignored for V4R4 and later versions.

       @param priority  The priority of the tasks running garbage collection.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setGarbageCollectionPriority(int priority) throws PropertyVetoException
    {
        if (priority == 10 || priority == 20 || priority == 30)
        {
            int old = this.garbageCollectionPriority_;
            vetoableChange_.fireVetoableChange("garbageCollectionPriority",new Integer(old),new Integer(priority));
            this.garbageCollectionPriority_ = priority;
            propertyChange_.firePropertyChange("garbageCollectionPriority",new Integer(old),new Integer(priority));
        }
        else
            throw new IllegalArgumentException("garbageCollectionPriority");
    }





    /**
       Sets whether all Java class files should be run interpretively.

       <p>The possible values are:
       <UL>
       <li>*OPTIMIZE Whether all Java classes are run interpretively depends on the
                     value specified for the OPTIMIZE property.  If
                     *INTERPRET, all Java classes will be run
                     interpretively.  If any other value was specified for the
                     OPTIMIZE property, only Java classes with Java programs created
                     using CRTJVAPGM command and specifying OPTIMIZE(*INTERPRET) will
                     be run interpretively.

       <li>*NO       Only Java classes with Java programs created using CRTJVAPGM
                     command and specifying OPTIMIZE(*INTERPRET) will be run
                     interpretively.  Java classes that need a Java program created
                     will be created with the optimization level specified in the
                     OPTIMIZE parameter.

       <li>*YES      All Java classes will be run interpretively regardless of the
                     OPTIMIZE value associated Java program.  Java classes that need
                     a Java program created will use the optimization level specified
                     in the OPTIMIZE parameter.

       <li>*JIT      All Java class files will be run using the just in time
                     compiler (JIT) regardless of the OPTIMIZE value
                     used when the associated Java program was created.

       @param interpret  How all Java class files should be run interpretively.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setInterpret(String interpret) throws PropertyVetoException
    {
        if (interpret != null)
        {
            if (interpret.equalsIgnoreCase("*OPTIMIZE") ||
                interpret.equalsIgnoreCase("*NO")       ||
                interpret.equalsIgnoreCase("*YES")      ||
                interpret.equalsIgnoreCase("*JIT"))         // @D2a
            {
                String old = this.interpret_;
                vetoableChange_.fireVetoableChange("interpret",old,interpret);
                this.interpret_ = interpret;
                propertyChange_.firePropertyChange("interpret",old,interpret);
            }
            else
                throw new IllegalArgumentException("interpret");
        }
        else
            throw new NullPointerException("interpret");
    }

    /**
       Sets the Java application to be run.

       @param  application The Java application to  be run.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setJavaApplication(String application) throws PropertyVetoException
    {
        if ((application != null) && (application.length() > 0))
        {
            String old = this.javaApplication_;
            vetoableChange_.fireVetoableChange("application", old, application);
            this.javaApplication_ = application;
            propertyChange_.firePropertyChange("application", old, application);
        }
        else
            throw new NullPointerException("application");
    }

    /**
       Sets the name that this job will run under.
       The maximum length allowed is 10 characters.

       <p>The possible values are:
       <UL>
       <li>QJVACMDSRV - The default job name.

       <li>*GEN       - The job name will be generated from the class name.

       <li>string     - Any generic string value.
       </UL>

       @param jobname The value of the job name.
       @since i5/OS V5R3M0
    **/
    public void setJobName(String jobname) // @F0A
    {
        if (jobname != null)
        {
            if (jobname.length() <= 10 &&
                jobname.length() > 0)
            {
                String old = this.jobname_;
                this.jobname_ = jobname;
                propertyChange_.firePropertyChange("jobname",old,jobname);
            }
            else
                throw new IllegalArgumentException("jobname");
        }
        else
            throw new NullPointerException("jobname");
    }

    /**
       Sets the optimization level of the IBM i system Java program that
       will be created if no Java program is associated with the Java
       class.  For Java classes that are in a class file, the created Java
       program remains associated with the class file after the Java
       program is run.  If the class is a part of a JAR or ZIP file, a
       temporary Java program is created to run from and then discarded.
       This can result in slow performance.  For best performance,
       explicitly create a Java program for JAR and ZIP files with the
       Create Java Program (CRTJVAPGM) command.

       For *INTERPRET, the resulting Java program interprets the
       class byte codes when invoked.  For other optimization levels, the
       Java program contains machine instruction sequences that are run
       when the Java program is invoked.

       *INTERPRET Java programs are smaller but run slower than
       Java programs created with higher optimization levels.  As you
       increase the optimization level beyond 10, the Java program
       performance generally improves, but the time required to create the
       Java program increases and debugging is more difficult.

       <p>The possible values are:
       <UL>
       <li>10          The Java program contains a compiled version of the class file
                       byte codes and has only minimal additional compiler
                       optimization.  Variables can be displayed and modified while
                       debugging.

       <li>*INTERPRET  The Java program created does not contain machine specific
                       instructions.  It will be interpreted when the program is
                       started.  Variables can be displayed and modified while
                       debugging.

                       If the INTERPRET keyword has a value of *OPTIMIZE, all of the
                       classes that run will be run interpreted even if there is an
                       optimized Java program associated with the class.

       <li>*JIT        No Java program containing machine instruction sequences
                       is created.  The class is run using the just in time
                       compiler (JIT).

       <li>20          The Java program contains a compiled version of the class file
                       byte codes and has some additional compiler optimization.
                       Variables can be displayed but not modified while debugging.

       <li>30          The Java program contains a compiled version of the class file
                       byte codes and has more compiler optimization than optimization
                       level 20.  During a debug session, user variables cannot be
                       changed, but can be displayed.  The presented values may not be
                       the current values of the variables.

       <li>40          The Java program contains a compiled version of the class file
                       byte codes and has more compiler optimization than optimization
                       level 30.  All call and instruction tracing is disabled.
       </UL>

       @param opt  The optimization level of the IBM i system Java program that
                   will be created if no Java program is associated with
                   the Java class.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setOptimization(String opt) throws PropertyVetoException
    {
        if (opt != null)
        {
            if (opt.equalsIgnoreCase("10")         ||
                opt.equalsIgnoreCase("*INTERPRET") ||
                opt.equalsIgnoreCase("*JIT")       ||   // @D2a
                opt.equalsIgnoreCase("20")         ||
                opt.equalsIgnoreCase("30")         ||
                opt.equalsIgnoreCase("40"))
            {
                String old = this.optimization_;
                vetoableChange_.fireVetoableChange("optimization",old,opt);
                this.optimization_ = opt;
                propertyChange_.firePropertyChange("optimization",old,opt);
            }
            else
                throw new IllegalArgumentException("optimization");
        }
        else
            throw new NullPointerException("optimization");
    }

    /**
       Sets special options used when running the Java class.  This
       method is not additive.  The list of values is replaced
       every time this method is called.

       <p>The possible values are:
       <UL>
       <li>*NONE      No special options are used when running the Java class.

       <li>*VERBOSE   A message is displayed each time a class file is loaded.

       <li>*DEBUG     Allows the system debugger to be used for this Java program.

       <li>*VERBOSEGC A message is displayed for each garbage collection sweep.

       <li>*NOCLASSGC Unused classes are not reclaimed when garbage collection is run.
       </UL>

       @param option  The special options used when running the Java class.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setOptions(String[] option) throws PropertyVetoException
    {
        if (option != null)
        {
            boolean valid = false;
            for (int i = 0 ; i < option.length ; i++)
            {
                valid = true;
                String s = option[i];
                if (!(s.equalsIgnoreCase("*NONE") || s.equalsIgnoreCase("*VERBOSE") || s.equalsIgnoreCase("*DEBUG")
                        || s.equalsIgnoreCase("*VERBOSEGC") || s.equalsIgnoreCase("*NOCLASSGC")))
                {
                    valid = false;
                    break;
                }
            }
            if (valid)
            {
                String[] old = this.options_;
                vetoableChange_.fireVetoableChange("options",old,option);
                this.options_ = option;
                propertyChange_.firePropertyChange("options",old,option);
            }
            else
                throw new IllegalArgumentException("option");
        }
        else
            throw new NullPointerException("option");
    }


    /**
       Sets one or more parameter values that are passed to the Java
       application.  A maximum of 200 parameter values can be passed.

       @param  parameters The parameters for the Java application.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setParameters(String[] parameters) throws PropertyVetoException
    {
        if (parameters != null)
        {
            String[] old = this.parameters_;
            vetoableChange_.fireVetoableChange("parameters", old, parameters);
            this.parameters_ = parameters;
            propertyChange_.firePropertyChange("parameters", old, parameters);
        }
        else
            throw new NullPointerException("parameters");
    }

    /**
       Sets the Java Virtual Machine properties when running the Java Application.

       @param  property The JVM properties.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setProperties(Properties property) throws PropertyVetoException
    {
        if (property != null) {
            Properties properties = property;

            if (properties.containsKey("os400.stdin"))
                properties.remove("os400.stdin");
            if (properties.containsKey("os400.stdout"))
                properties.remove("os400.stdout");
            if (properties.containsKey("os400.stderr"))
                properties.remove("os400.stderr");
            if (properties.containsKey("os400.stdio.convert"))
                properties.remove("os400.stdio.convert");

            //save properties
            Properties old = this.properties_;
            vetoableChange_.fireVetoableChange("properties", old, properties);
            this.properties_ = (Properties)properties.clone();
            propertyChange_.firePropertyChange("properties", old, properties);

        }else
            throw new NullPointerException("properties");
    }

    /**
       Sets the level of warnings given for directories in
       CLASSPATH that have public write authority.  A directory in
       CLASSPATH that has public write authority is a security exposure
       because it may contain a class file with the same name as the one
       you want to run. Whichever class file is found first is run.

       <p>The possible values are:
       <UL>
       <li>*WARN Warning message is sent for each directory in the CLASSPATH
                 that has public write authority.

       <li>*SECURE  A warning message is sent for each directory in the CLASSPATH
                    that has public write authority.  If one or more warning
                    messages are sent, an escape message is sent and the Java
                    program is not run.

       <li>*IGNORE  Ignore the fact that directories in the CLASSPATH may have
                    public write authority.  No warnings messages are sent.
       </UL>

       @param  chklvl  The level of warnings given for directories in the
                       CLASSPATH that have public write authority.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setSecurityCheckLevel(String chklvl) throws PropertyVetoException
    {
        if (chklvl != null)
        {
            if (chklvl.equalsIgnoreCase("*WARN") || chklvl.equalsIgnoreCase("*SECURE") || chklvl.equalsIgnoreCase("*IGNORE"))
            {
                String old = this.securityCheckLevel_;
                vetoableChange_.fireVetoableChange("securityCheckLevel",old,chklvl);
                this.securityCheckLevel_ = chklvl;
                propertyChange_.firePropertyChange("securityCheckLevel",old,chklvl);
            }
            else
                throw new IllegalArgumentException("securityCheckLevel");
        }
        else
            throw new NullPointerException("securityCheckLevel");
    }

    /**
       Sets the system.

       @param system     The system.
       @exception PropertyVetoException If the change is vetoed.
    **/
    public void setSystem(AS400 system) throws PropertyVetoException
    {
        if (system != null)
        {
            AS400 old = this.as400_;
            vetoableChange_.fireVetoableChange("as400", old, system);
            this.as400_ = system;
            propertyChange_.firePropertyChange("as400", old, system);
        }
        else
            throw new NullPointerException("as400");
    }

}

