/* $Id: Kernel.java,v 1.3 1998/11/17 22:04:22 thain Exp thain $ */

import java.util.*;
import java.io.*;
import java.lang.reflect.*;

/** A simple kernel simulation.
 *
 * There is only one public interface to this class: interrupt().
 * System calls, disk notification, and power on messages all arrive
 * by way of this function.
 *
 * @author Douglas Thain (thain@cs.wisc.edu)
 * @see Disk
 * @see Library
 */

public class Kernel {

    /** Indicates that a user caused the interrupt.
    * <br><b>Parameter i1</b> -- a valid system call number.
    * Other parameters depend on the call number.<br>
    */
    public static final int INTERRUPT_USER=0;

    /** Indicates that a disk caused the interrupt.
    * All other parameters will be null or zero.
    */
    public static final int INTERRUPT_DISK=1;

    /** Indicates that it's time to start the system.
    * <br>The Kernel should set up any internal state and
    * begin executing the first program.
    * <br><b>Parameter i1</b> --  the number of blocks to use in the
    * disk cache.<br>
    * <b>Parameter o1</b> -- the Disk to be used.
    */
    public static final int INTERRUPT_POWER_ON=2;

    /** System call to output text on the console.
    * <br><b>Parameter o1</b>  -- A string to display<br>
    * <b>Returns</b> -- Zero.
    */
    public static final int SYSCALL_OUTPUT=0;

    /** System call to read text from the console.
    * <br>This function returns when the user presses [return].<br>
    * <b>Parameter o1</b> -- A StringBuffer to fill with input text.<br>
    * <b>Returns</b> -- Zero, ERROR_BAD_ARGUMENT_TYPE, ERROR_END_OF_FILE,
    * or ERROR_IO.<br>
    */
    public static final int SYSCALL_INPUT=1;

    /** System call to execute a new program.
    * <br>The new program will run in parallel to the current program.<br>
    * <b>Parameter o1</b> - The name of a Java class to execute.<br>
    * <b>Parameter o2</b> - An array for String arguments.<br>
    * <b>Returns</b> Zero, ERROR_BAD_ARGUMENT_TYPE,
    * or ERROR_BAD_COMMAND.<br>
    */
    public static final int SYSCALL_EXEC=2;

    /** System call to execute a new program.
    * <br>This call will not return until the newly created program has
    * run to completion.<br>
    * <b>Parameter o1 </b> -- The name of a Java class to execute.<br>
    * <b>Parameter o2 </b> -- An array for String arguments.<br>
    * <b>Returns</b> -- Zero, ERROR_BAD_ARGUMENT TYPE,
    * ERROR_BAD_COMMAND, or ERROR_IN_CHILD.<br>
    */
    public static final int SYSCALL_EXEC_AND_WAIT=3;

    /** One of the system call parameters made no sense */
    public static final int ERROR_BAD_ARGUMENT_TYPE=-1;

    /** A command passed to SYSCALL_EXEC could not be executed */
    public static final int ERROR_BAD_COMMAND=-2;

    /** One parameter was too big or too small */
    public static final int ERROR_OUT_OF_RANGE=-3;

    /** End of file was reached. */
    public static final int ERROR_END_OF_FILE=-4;

    /** Miscellaneous error while performing I/O. */
    public static final int ERROR_IO=-5;

    /** A child program caused an exception and crashed. */
    public static final int ERROR_IN_CHILD=-6;

    /** The disk to be used */
    private static Disk disk;

    /** The size of the buffer cache */
    private static int bufferSize;

    /** This is the only entry into the kernel.
    * <p>A user may call this function to perform a system call.
    * In that case, set <tt>kind</tt> to <tt>INTERRUPT_USER</tt>
    * and set <tt>i1</tt> to the system call number.  Other
    * parameters should be set as the system call requires.<p>
    * A disk may call this function to indicate the current operation
    * has completed.  In that case, <tt>kind</tt> will be
    * <tt>INTERRUPT_DISK</tt> and all parameters will be zero or null.
    * <b>Important:</b> If the Disk calls <tt>interrupt()</tt>, the
    * Kernel should take care of business and return from the interrupt
    * as soon as possible.  All Disk I/O is halted while the interrupt is
    * being processed. <p>
    * The boot code may call this function to indicate that the computer
    * has been turned on and it is time to start the first program
    * and use the disk.  In that case, <tt>kind</tt> will be
    * <tt>INTERRUPT_POWER_ON</tt>, o1 will point to the Disk to be
    * used, i1 will indicate the size of the buffer cache,
    * and all other parameters will be zero or null.
    * @return A negative number indicating an error code, or other
    * values depending on the system call.
    */
    
    public static int interrupt( int kind, int i1, int i2,
        Object o1, Object o2, byte a[] ) {

        try {
            switch(kind) {
                case INTERRUPT_USER:
                switch(i1) {
                    case SYSCALL_OUTPUT:
                    return doOutput((String)o1);

                    case SYSCALL_INPUT:
                    return doInput((StringBuffer)o1);

                    case SYSCALL_EXEC:
                    return doExec((String)o1,(String[])o2);

                    case SYSCALL_EXEC_AND_WAIT:
                    return doExecAndWait((String)o1,
                        (String[])o2);

                }
                break;

                case INTERRUPT_DISK:
                break;

                case INTERRUPT_POWER_ON:
                doPowerOn(i1,o1);
                break;

            }
        } catch( Exception e ) {
            // Most likely, we arrived here due to a bad cast. 
            return ERROR_BAD_ARGUMENT_TYPE;
        }

        return 0;
    }

    /** Load the initial shell, and perform any other necessary startup. */

    private static void doPowerOn( int i, Object o ) {
        bufferSize = i;
        disk = (Disk)o;
        doOutput("Kernel: Buffer size is "+i+" blocks\n");
        doOutput("Kernel: Disk is "+disk.getSize()+" blocks\n");
        doOutput("Kernel: Loading initial program.\n");
        if(doExecAndWait("Shell",null)<0) {
            doOutput("Kernel: Unable to load initial program!\n");
        } else {
            doOutput("Kernel: Initial program complete.\n");
        }

        // *** BEGIN CHANGE
        Launcher.joinAll();
        // *** END CHANGE
    }

    /** Display a string on the console. */

    private static int doOutput(String s) {
        System.out.print(s);
        return 0;
    }

    // *** BEGIN CHANGE
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    // *** END CHANGE

    /** Read a line from the console into a StringBuffer. */

    private static int doInput(StringBuffer sb) {
        try {
            // *** BEGIN CHANGE
            // Line deleted: BufferedReader br ...
            // *** END CHANGE
            
            String s = br.readLine();
            if( s==null ) return ERROR_END_OF_FILE;
            sb.append(s);
            return 0;
        } catch(Throwable t) {
            return ERROR_IO;
        }
    }

    /** Load a program, call run() and return when complete.
    * This looks for a class in the current classpath.
    * If the class cannot be loaded, ERROR_BAD_COMMAND is returned.
    * If the new program causes throws an uncaught exception,
    * ERROR_IN_CHILD is returned.
    */

    private static int doExecAndWait(String command, String args[]) {
        Launcher l;
        try {
            l = new Launcher( command, args );
        } catch(Exception e) {
            return ERROR_BAD_COMMAND;
        }
        try {
            l.go();
            return 0;
        } catch(Exception e) {
            return ERROR_IN_CHILD;
        }

    }

    /** Load a program and run it in parallel to the current program.
    * Do not wait for it to complete.
    * This looks for a class in the current classpath.
    * If the class cannot be loaded, ERROR_BAD_COMMAND is returned.
    */

    private static int doExec(String command, String args[]) {
        try {
            Launcher l = new Launcher( command, args );
            l.start();
            return 0;
        } catch(Exception e) {
            return ERROR_BAD_COMMAND;
        }
    }

    // *** BEGIN CHANGE
    // *** Replace all of you old class Launcher with this new Launcher

    /* This class finds a Java program on disk, makes sure that
     * it has an appropriate main(), and launches the program.
     * No user-servicable parts in here.
     */

    static private class Launcher extends Thread {

        static private Launcher list=null;

        private Method method;
        private Object arglist[];
        private Launcher next=null;

        public Launcher( String command, String args[] )
        throws Exception {

            /* If the user supplied no args, make a dummy. */
            if( args==null ) {
                args = new String[1];
                args[0] = null;
            }

            /* Create an array of the method types */
            Class params[] = new Class[1];
            params[0] = args.getClass();

            /* Find the program and look up its main method */
            Class programClass = Class.forName(command);
            method = programClass.getMethod("main",params); 

            /* Assemble an argument list for the method. */
            arglist = new Object[1];
            arglist[0] = args;

            /* Add this object to head of the list */
            next = list;
            list = this;
        }   

        public void run() {
            /* If an async program throws, just swallow it */
            try {
                go();
            } catch(Exception e) {
            }
        }

        public void go() throws Exception {
            /* Launch the method using the arglist */
            try {
                method.invoke(null,arglist);

            } catch(InvocationTargetException e) {
                /* Give the user a message */
                System.out.println("Kernel: User error:");
                e.getTargetException().printStackTrace();

                /* Rethrow the exception to cause an */
                /* appropriate return value */
                throw e;
            } catch(Exception e) {
                System.out.println("Kernel: "+e);
            }
        }

        static public void joinAll() {
            Launcher l = list;
            while( l!=null ) {
                try {
                    l.join();
                } catch(Exception e) {
                    System.out.println("Kernel: join: "+e);
                }
                l=l.next;
            }
        }
    }

    // *** END CHANGE
}

