/*
 * Copyright 1999-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.launcher;

import java.io.File;
import java.io.InputStream;
import java.io.IOException;

/**
 * A class for detecting if the parent JVM that launched this process has
 * terminated.
 *
 * @author Patrick Luby
 */
public class ParentListener extends Thread {

    //------------------------------------------------------------------ Fields

    /**
     * Cached heartbeat file.
     */
    private File heartbeatFile = null;

    //------------------------------------------------------------ Constructors

    /**
     * Validates and caches a lock file created by the parent JVM.
     *
     * @param path the lock file that the parent JVM has an open
     *  FileOutputStream
     * @throws IOException if the heartbeat cannot be converted into a valid
     *  File object
     */
    public ParentListener(String path) throws IOException {

        if (path == null)
            throw new IOException();

        // Make sure we have a valid path
        heartbeatFile = new File(path);
        heartbeatFile.getCanonicalPath();

    }

    //----------------------------------------------------------------- Methods

    /**
     * Periodically check that the parent JVM has not terminated. On all
     * platforms other than Windows, this method will check that System.in has
     * not been closed. On Windows NT, 2000, and XP the lock file specified in
     * the {@link #ParentListener(String)} constructor is monitored as reading
     * System.in will block the entire process on Windows machines that use
     * some versions of Unix shells such as MKS, etc. No monitoring is done
     * on Window 95, 98, and ME.
     */ 
    public void run() {

        String osname = System.getProperty("os.name").toLowerCase();

        // We need to use file locking on Windows since reading System.in
        // will block the entire process on some Windows machines.
        if (osname.indexOf("windows") >= 0) {

            // Do nothing if this is a Windows 9x platform since our file
            // locking mechanism does not work on the early versions of
            // Windows
            if (osname.indexOf("nt") == -1 && osname.indexOf("2000") == -1 && osname.indexOf("xp") == -1)
                return;

            // If we can delete the heartbeatFile on Windows, it means that
            // the parent JVM has closed its FileOutputStream on the file.
            // Note that the parent JVM's stream should only be closed when
            // it exits.
            for ( ; ; ) {
                if (heartbeatFile.delete())
                    break;
                // Wait awhile before we try again
                yield();
                try {
                    sleep(5000);
                } catch (Exception e) {}
            }

        } else {

            // Cache System.in in case the application redirects
            InputStream is = System.in;
            int bytesAvailable = 0;
            int bytesRead = 0;
            byte[] buf = new byte[1024];
            try {
                while (true) {
                    synchronized (is) {
                        // Mark the stream position so that other threads can
                        // reread the strea
                        is.mark(buf.length);
                        // Read one more byte than has already been read to
                        // force the stream to wait for input
                        bytesAvailable = is.available();
                        if (bytesAvailable < buf.length) {
                            bytesRead = is.read(buf, 0, bytesAvailable + 1);
                            // Reset so that we "unread" the bytes that we read
                            is.reset();
                            if (bytesRead == -1)
                                break;
                        } else {
                            // Make the buffer larger
                            if (buf.length < Integer.MAX_VALUE / 2)
                                buf = new byte[buf.length * 2];
                        }
                    }
                    yield();
                }
            } catch (IOException ioe) {}

        }

        // Clean up before exiting
        if (heartbeatFile != null)
            heartbeatFile.delete();

        // Exit this process since the parent JVM has exited
        System.exit(0);

    }

}
