/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.remote;

import java.io.*;
import edu.emory.mathcs.util.remote.server.*;
import edu.emory.mathcs.util.remote.io.*;
import edu.emory.mathcs.util.remote.io.server.*;
import java.rmi.RemoteException;
import edu.emory.mathcs.util.remote.server.impl.*;

/**
 * Serializable handle to a native process running on a remote machine.
 * Communicates via RMI with the process handle at the server
 * side. Since the handle is serializable, it can be sent over RMI (e.g.
 * returned from a remote call). For example:
 *
 * <pre>
 * RemoteProcess getRemoteProcess() throws RemoteException {
 *    // locate (or start) nativeProcess
 *    // ...
 *    return new RemoteProcess(nativeProcess);
 * }
 * </pre>
 *
 * @author Dawid Kurzyniec
 * @version 1.0
 */

public class RemoteProcess implements Externalizable {
    RemoteProcessSrv server;
    transient RemoteInputStream srvin;
    transient RemoteInputStream srverr;
    transient RemoteOutputStream srvout;

    /** for deserialization only */
    public RemoteProcess() {}

    /**
     * Constructs a new serializable remote reference to the specified
     * process.
     *
     * @param process the process to create the reference to
     */
    public RemoteProcess(final Process process) {
        this(new RemoteProcessSrvImpl(process));
    }

    /**
     * Constructs a new serializable remote reference to a remote
     * process. Use this constructor on the client side if
     * the RMI handle to the process has been already created.
     *
     * @param server the RMI handle to the process
     */
    public RemoteProcess(RemoteProcessSrv server) {
        if (server == null) {
            throw new NullPointerException("server");
        }
        this.server = server;
        initStreams();
    }

    private void initStreams() {
        this.srvin = new RemoteInputStream(new RemoteInputStreamSrv() {
            public byte[] read(int maxRead) throws IOException {
                return server.readFromIn(maxRead);
            }
            public long skip(long n) throws IOException {
                return server.skipInIn(n);
            }
            public int available() throws IOException {
                return server.availableInIn();
            }
            public void close() throws IOException {
                server.closeIn();
            }
        });
        this.srverr = new RemoteInputStream(new RemoteInputStreamSrv() {
            public byte[] read(int maxRead) throws IOException {
                return server.readFromErr(maxRead);
            }
            public long skip(long n) throws IOException {
                return server.skipInErr(n);
            }
            public int available() throws IOException {
                return server.availableInErr();
            }
            public void close() throws IOException {
                server.closeErr();
            }
        });
        this.srvout = new RemoteOutputStream(new RemoteOutputStreamSrv() {
            public void write(byte[] data) throws IOException {
                server.writeToOut(data);
            }
            public void flush() throws IOException {
                server.flushOut();
            }
            public void close() throws IOException {
                server.closeOut();
            }
        });

    }

    /**
     * Destroys the remote process. The process is forcibly terminated.
     *
     * @throws RemoteException upon a communication failure
     */
    public void destroy() throws RemoteException {
        server.destroy();
    }

    /**
     * Returns the exit value for the remote process.
     *
     * @return  the exit value of the remote process. By convention, the value
     *          <code>0</code> indicates normal termination.
     * @throws  RemoteException upon a communication failure
     * @throws  IllegalThreadStateException  if the remote process
     *          has not yet terminated
     */
    public int exitValue() throws RemoteException {
        return server.exitValue();
    }

    /**
     * Causes the current thread to wait, if necessary, until the
     * remote process has terminated. This method returns
     * immediately if the remote process has already terminated. If the
     * remote process has not yet terminated, the calling thread will be
     * blocked until it does.
     *
     * @return  the exit value of the process. By convention,
     *          <code>0</code> indicates normal termination
     * @throws  RemoteException upon a communication failure
     * @throws  InterruptedException  if the current thread is
     *          {@link Thread#interrupt() interrupted} by another thread
     *          while it is waiting
     */
    public int waitFor() throws RemoteException, InterruptedException {
        return server.waitFor();
    }

    /**
     * Gets the remote input stream of the remote process.
     * The stream obtains data piped from the standard output stream
     * of the native remote process.
     * <p>
     * Implementation note: It is a good idea for the input stream to
     * be buffered.
     *
     * @return  the input stream connected to the standard output of the
     *          remote process
     * @throws  RemoteException upon a communication failure
     */
    public InputStream getInputStream() {
        return srvin;
    }

    /**
     * Gets the remote error stream of the remote process.
     * The stream obtains data piped from the standard error stream
     * of the native remote process.
     * <p>
     * Implementation note: It is a good idea for the input stream to
     * be buffered.
     *
     * @return  the input stream connected to the standard error of the
     *          remote process
     * @throws  RemoteException upon a communication failure
     */
    public InputStream getErrorStream() {
        return srverr;
    }

    /**
     * Gets the remote output stream of the remote process.
     * Output to the stream is piped into the standard input stream of
     * the native remote process.
     * <p>
     * Implementation note: It is a good idea for the output stream to
     * be buffered.
     *
     * @return  the output stream connected to the standard input of the
     *          remote process.
     */
    public OutputStream getOutputStream() {
        return srvout;
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(server);
    }
    public void readExternal(ObjectInput in)
        throws IOException, ClassNotFoundException
    {
        server = (RemoteProcessSrv)in.readObject();
        initStreams();
    }
}
