package com.mcqueeney.demo;

/**
 * Demonstrate code to shutdown after ^C only after processing
 * a complete record, not in the middle.
 * @author Tom McQueeney
 */
public class CleanShutdownDemo {
    
    /** Flags so we can stop cleanly when the VM exits. */
    private boolean isVMShuttingDown = false;
    private boolean readyToExit = false;

    // Shutdown thread as an instance var so we can remove it later.
    private Thread shutdownThread; 

    // For demonstration purposes:
    private final boolean runningInCleanMode;
    private StringBuffer[] dataToProcess;


    /**
     * Main to start processing with some string data.
     * @param args processes "cleanly" if args[0] is "clean"
     */
    public static void main(String[] args) {
        StringBuffer[] data = new StringBuffer[] {
            new StringBuffer("show"),
            new StringBuffer("cod"),
            new StringBuffer("thread"),
            new StringBuffer("blogg"),
            new StringBuffer("vacation"),
        };
 
        boolean runClean = args.length > 0 && "clean".equals(args[0]);
        
        new CleanShutdownDemo(runClean).startProcessing(data);
    }

    
    /**
     * Constructor to set what demo "mode" we should run in
     * and to register the shutdown hook.
     */
    public CleanShutdownDemo(boolean shouldRunClean) {
        this.runningInCleanMode = shouldRunClean;
        registerShutdownHook(); // For cleaner unexpected shutdowns.
    }

    /**
     * Begins processing the records, cognizant of Control-C interrupts.
     * Param "records" is the data we want to process in a way that 
     * an interrupt occurs only after fully processing each record. 
     * The records probably would be data from a ResultSet or some
     * other data we need to process without being interrupted
     * in the middle.
     */
    public void startProcessing(StringBuffer[] records) {
        this.dataToProcess = records; // Store for demo purposes.
        
        System.out.println("Starting processing");
        int recordsProcessed = 0;

        try {
            for (int i = 0, j = records.length; i < j; i++) {
                // Process this next record but don't let ^C interrupt
                // unless it takes more than 1.5 seconds.
                addIngToString(records[i]);
                ++recordsProcessed;

                // Don't continue if VM is trying to shut down.
                if (this.isVMShuttingDown) {
                    System.out.println(
                        "Process interrupted. Shutting down after processing " +
                        recordsProcessed + " records."
                    );

                    signalReadyToExit();
                    break;
                }
            } // end while.

            // Tell shutdown hook we're done.
            signalReadyToExit(); // In case shutdown thread running.
            unregisterShutdownHook();
            
            System.out.println(
                "Finished comparison. Processed " + recordsProcessed +
                " records."
            );
        } catch (RuntimeException rte) {
            System.err.println(
                "Got unexpected runtime exception: " + rte.getMessage()
            );
            throw rte;
        } finally {
			// Cleanup, assuming we had resources to close.
        }
    }
    
    /** Set instance flag to tell everyone we're ready to exit. */
    private synchronized void signalReadyToExit() {
        this.readyToExit = true;
        this.notify();
    }

    /**
     * This is our demo method that allegedly is doing the 
     * processing we want to protect.
     * Takes 1 second to append "ing" onto the given stringbuffer.
     * @param buffer the stringbuffer onto which to append "ing"
     */
    private void addIngToString(StringBuffer buffer) {
        char[] toAppend = { 'i', 'n', 'g' };
        
        for (int i = 0, j = toAppend.length; i < j; i++) {
            System.out.print(
                "About to add '" + toAppend[i] + "' to " + buffer
            ); 
            sleep(333); // Sleep to simulate long processing
            buffer.append(toAppend[i]);
            System.out.println("...added");
        }
    }

    /**
     * Registers a shutdown hook to pause shutdown after Control-C long enough
     * to finish processing the current record.
     */
    private void registerShutdownHook() {
        System.out.println("Registering shutdown hook");
        
        this.shutdownThread = new Thread("myhook") {
            public void run() {
                // For demonstration purposes: Don't give chance to 
                // shutdown unless flag is set. Just show data.
                if (!runningInCleanMode) {
                    showData();
                    return;
                }
                
                synchronized(this) {
                    if (!readyToExit) {
                        isVMShuttingDown = true;
                        System.out.println("Shutdown hook: Waiting to exit");
                        try {
                            // Wait up to 1.5 secs for a record to be processed.
                            wait(1500);
                        } catch (InterruptedException ignore) {
                        }
                        if (!readyToExit) {
                            System.out.println(
                                "Main processing interrupted." +
                                " Data corruption possible."
                            );
                        }
                    }
                }
                
                showData(); // To demo current state of data.
            }

            /**
             * For demo purposes: Show data to see whether it is "corrupted" 
             */
            private void showData() {
                for (int i = 0, j = dataToProcess.length; i < j; i++) {
                    System.err.println(
                        "Record " + i + ": " + dataToProcess[i]
                    );
                }
            }
        };
        
        Runtime.getRuntime().addShutdownHook(this.shutdownThread);
    }
    
    /** Unregister the shutdown hook. */
    private void unregisterShutdownHook() {
        System.out.println("Unregistering shutdown hook");
        try {
            Runtime.getRuntime().removeShutdownHook(this.shutdownThread);
        } catch (IllegalStateException ignore) {
            // VM already was shutting down
        }
    }

    /**
     * Sleeps for the given amount of milliseconds. 
     * @param millisToSleep time to sleep in millis.
     */
    private void sleep(int millisToSleep) {
        try {
            Thread.sleep(millisToSleep);
        } catch (InterruptedException ignore) {
        }
    }

}
