com.bluemarsh.jswat.console.Main.java Source code

Java tutorial

Introduction

Here is the source code for com.bluemarsh.jswat.console.Main.java

Source

/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 *
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is JSwat. The Initial Developer of the Original
 * Software is Nathan L. Fiedler. Portions created by Nathan L. Fiedler
 * are Copyright (C) 2009. All Rights Reserved.
 *
 * Contributor(s): Nathan L. Fiedler.
 *
 * $Id$
 */
package com.bluemarsh.jswat.console;

import com.bluemarsh.jswat.command.CommandException;
import com.bluemarsh.jswat.command.CommandParser;
import com.bluemarsh.jswat.command.CommandProvider;
import com.bluemarsh.jswat.command.MissingArgumentsException;
import com.bluemarsh.jswat.core.PlatformProvider;
import com.bluemarsh.jswat.core.connect.ConnectionEvent;
import com.bluemarsh.jswat.core.connect.ConnectionFactory;
import com.bluemarsh.jswat.core.connect.ConnectionListener;
import com.bluemarsh.jswat.core.connect.ConnectionProvider;
import com.bluemarsh.jswat.core.connect.JvmConnection;
import com.bluemarsh.jswat.core.path.PathManager;
import com.bluemarsh.jswat.core.path.PathProvider;
import com.bluemarsh.jswat.core.runtime.RuntimeManager;
import com.bluemarsh.jswat.core.runtime.RuntimeProvider;
import com.bluemarsh.jswat.core.session.Session;
import com.bluemarsh.jswat.core.session.SessionManager;
import com.bluemarsh.jswat.core.session.SessionProvider;
import com.bluemarsh.jswat.core.util.Strings;
import com.sun.jdi.Bootstrap;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.openide.util.NbBundle;

/**
 * Bootstrap class for the console interface of JSwat. Initializes all
 * of the necessary services and registers event listeners. Enters a
 * loop to process using input.
 *
 * @author  Nathan Fiedler
 */
public class Main {

    /** If true, the debugger attempts to emulate JDB output. */
    private static boolean jdbEmulationMode;
    /** Logger for gracefully reporting unexpected errors. */
    private static final Logger logger = Logger.getLogger(Main.class.getName());

    /**
     * Creates a new instance of Main.
     */
    private Main() {
    }

    /**
     * Kicks off the application.
     *
     * @param  args  the command line arguments.
     */
    public static void main(String[] args) {
        //
        // An attempt was made early on to use the Console class in java.io,
        // but that is not designed to handle asynchronous output from
        // multiple threads, so we just use the usual System.out for output
        // and System.in for input. Note that automatic flushing is used to
        // ensure output is shown in a timely fashion.
        //

        //
        // Where console mode seems to work:
        // - bash
        // - emacs
        //
        // Where console mode does not seem to work:
        // - NetBeans: output from event listeners is never shown and the
        //   cursor sometimes lags behind the output
        //

        // Turn on flushing so printing the prompt will flush
        // all buffered output generated from other threads.
        PrintWriter output = new PrintWriter(System.out, true);

        // Make sure we have the JPDA classes.
        try {
            Bootstrap.virtualMachineManager();
        } catch (NoClassDefFoundError ncdfe) {
            output.println(NbBundle.getMessage(Main.class, "MSG_Main_NoJPDA"));
            System.exit(1);
        }

        // Ensure we can create the user directory by requesting the
        // platform service. Simply asking for it has the desired effect.
        PlatformProvider.getPlatformService();

        // Define the logging configuration.
        LogManager manager = LogManager.getLogManager();
        InputStream is = Main.class.getResourceAsStream("logging.properties");
        try {
            manager.readConfiguration(is);
        } catch (IOException ioe) {
            ioe.printStackTrace();
            System.exit(1);
        }

        // Print out some useful debugging information.
        logSystemDetails();

        // Add a shutdown hook to make sure we exit cleanly.
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

            @Override
            public void run() {
                // Save the command aliases.
                CommandParser parser = CommandProvider.getCommandParser();
                parser.saveSettings();
                // Save the runtimes to persistent storage.
                RuntimeManager rm = RuntimeProvider.getRuntimeManager();
                rm.saveRuntimes();
                // Save the sessions to persistent storage and have them
                // close down in preparation to exit.
                SessionManager sm = SessionProvider.getSessionManager();
                sm.saveSessions(true);
            }
        }));

        // Initialize the command parser and load the aliases.
        CommandParser parser = CommandProvider.getCommandParser();
        parser.loadSettings();
        parser.setOutput(output);

        // Create an OutputAdapter to display debuggee output.
        OutputAdapter adapter = new OutputAdapter(output);
        SessionManager sessionMgr = SessionProvider.getSessionManager();
        sessionMgr.addSessionManagerListener(adapter);
        // Create a SessionWatcher to monitor the session status.
        SessionWatcher swatcher = new SessionWatcher();
        sessionMgr.addSessionManagerListener(swatcher);
        // Create a BreakpointWatcher to monitor the breakpoints.
        BreakpointWatcher bwatcher = new BreakpointWatcher();
        sessionMgr.addSessionManagerListener(bwatcher);

        // Add the watchers and adapters to the open sessions.
        Iterator<Session> iter = sessionMgr.iterateSessions();
        while (iter.hasNext()) {
            Session s = iter.next();
            s.addSessionListener(adapter);
            s.addSessionListener(swatcher);
        }

        // Find and run the RC file.
        try {
            runStartupFile(parser, output);
        } catch (IOException ioe) {
            logger.log(Level.SEVERE, null, ioe);
        }

        // Process command line arguments.
        try {
            processArguments(args);
        } catch (ParseException pe) {
            // Report the problem and keep going.
            System.err.println("Option parsing failed: " + pe.getMessage());
            logger.log(Level.SEVERE, null, pe);
        }

        // Display a helpful greeting.
        output.println(NbBundle.getMessage(Main.class, "MSG_Main_Welcome"));
        if (jdbEmulationMode) {
            output.println(NbBundle.getMessage(Main.class, "MSG_Main_Jdb_Emulation"));
        }

        // Enter the main loop of processing user input.
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            // Keep the prompt format identical to jdb for compatibility
            // with emacs and other possible wrappers.
            output.print("> ");
            output.flush();
            try {
                String command = input.readLine();
                // A null value indicates end of stream.
                if (command != null) {
                    performCommand(output, parser, command);
                }
                // Sleep briefly to give the event processing threads,
                // and the automatic output flushing, a chance to catch
                // up before printing the input prompt again.
                Thread.sleep(250);
            } catch (InterruptedException ie) {
                logger.log(Level.WARNING, null, ie);
            } catch (IOException ioe) {
                logger.log(Level.SEVERE, null, ioe);
                output.println(NbBundle.getMessage(Main.class, "ERR_Main_IOError", ioe));
            } catch (Exception x) {
                // Don't ever let an internal bug (e.g. in the command parser)
                // hork the console, as it really irritates people.
                logger.log(Level.SEVERE, null, x);
                output.println(NbBundle.getMessage(Main.class, "ERR_Main_Exception", x));
            }
        }
    }

    /**
     * Interprets the given command via the command parser.
     *
     * @param  output  where to write error messages.
     * @param  parser  the command interpreter.
     * @param  input   the input command.
     */
    private static void performCommand(PrintWriter output, CommandParser parser, String input) {
        // Send the input to the command parser, which will run the
        // command and send output to the writer it was assigned earlier.
        try {
            parser.parseInput(input);
        } catch (MissingArgumentsException mae) {
            output.println(mae.getMessage());
            output.println(NbBundle.getMessage(Main.class, "ERR_Main_HelpCommand"));
        } catch (CommandException ce) {
            // Print the message which should explain everything.
            // If there is a root cause, show that, too.
            output.println(ce.getMessage());
            Throwable cause = ce.getCause();
            if (cause != null) {
                String cmsg = cause.getMessage();
                if (cmsg != null) {
                    output.println(cmsg);
                }
            }
        }
    }

    /**
     * Sends system information to the log for debugging purposes.
     */
    private static void logSystemDetails() {
        logger.info(String.format("Log Session: %tc", new Date()));
        String version = NbBundle.getMessage(Main.class, "MSG_Main_version");
        logger.info(String.format("Product version: %s", version));
        logger.info(String.format("Operating System: %s %s on %s", System.getProperty("os.name"),
                System.getProperty("os.version"), System.getProperty("os.arch")));
        logger.info(String.format("Java; VM; Vendor: %s; %s %s; %s", System.getProperty("java.version"),
                System.getProperty("java.vm.name"), System.getProperty("java.vm.version"),
                System.getProperty("java.vendor")));
        logger.info(String.format("Java Home: %s", System.getProperty("java.home")));
        logger.info(String.format("Home Directory: %s", System.getProperty("user.home")));
        logger.info(String.format("Current Directory: %s", System.getProperty("user.dir")));
        logger.info(String.format("Class Path: %s", System.getProperty("java.class.path")));
    }

    /**
     * Find the startup file in one of several locations and by one
     * of several names, then run the commands found therein.
     *
     * @param  parser  the command interpreter.
     * @throws  IOException  if reading file fails.
     */
    private static void runStartupFile(CommandParser parser, PrintWriter consoleOutput) throws IOException {
        File[] files = { new File(System.getProperty("user.dir"), ".jswatrc"),
                new File(System.getProperty("user.dir"), "jswat.ini"),
                new File(System.getProperty("user.home"), ".jswatrc"),
                new File(System.getProperty("user.home"), "jswat.ini") };
        for (File file : files) {
            if (file.canRead()) {
                consoleOutput.println("Executing startup file: " + file.getAbsolutePath());
                BufferedReader br = new BufferedReader(new FileReader(file));
                try {
                    String line = br.readLine();
                    while (line != null) {
                        line = line.trim();
                        if (!line.isEmpty() && !line.startsWith("#")) {
                            performCommand(consoleOutput, parser, line);
                        }
                        line = br.readLine();
                    }
                } finally {
                    br.close();
                }
                // We just read the first file we find and stop.
                break;
            }
        }
    }

    /**
     * Process the given command line arguments.
     *
     * @param  args  command line arguments.
     * @throws  ParseException  if argument parsing fails.
     */
    private static void processArguments(String[] args) throws ParseException {
        Options options = new Options();
        // Option: h/help
        OptionBuilder.withDescription(NbBundle.getMessage(Main.class, "MSG_Main_Option_help"));
        OptionBuilder.withLongOpt("help");
        options.addOption(OptionBuilder.create("h"));

        // Option: attach <port>
        OptionBuilder.hasArg();
        OptionBuilder.withArgName("port");
        OptionBuilder.withDescription(NbBundle.getMessage(Main.class, "MSG_Main_Option_attach"));
        options.addOption(OptionBuilder.create("attach"));

        // Option: sourcepath <path>
        OptionBuilder.hasArg();
        OptionBuilder.withArgName("path");
        OptionBuilder.withDescription(NbBundle.getMessage(Main.class, "MSG_Main_Option_sourcepath"));
        options.addOption(OptionBuilder.create("sourcepath"));

        // Option: e/emacs
        OptionBuilder.withDescription(NbBundle.getMessage(Main.class, "MSG_Main_Option_jdb"));
        options.addOption(OptionBuilder.create("jdb"));

        // Parse the command line arguments.
        CommandLineParser parser = new GnuParser();
        CommandLine line = parser.parse(options, args);

        // Interrogate the command line options.
        jdbEmulationMode = line.hasOption("jdb");
        if (line.hasOption("help")) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("java com.bluemarsh.jswat.console.Main", options);
            System.exit(0);
        }
        if (line.hasOption("sourcepath")) {
            Session session = SessionProvider.getCurrentSession();
            PathManager pm = PathProvider.getPathManager(session);
            String path = line.getOptionValue("sourcepath");
            List<String> roots = Strings.stringToList(path, File.pathSeparator);
            pm.setSourcePath(roots);
        }
        if (line.hasOption("attach")) {
            final Session session = SessionProvider.getCurrentSession();
            String port = line.getOptionValue("attach");
            ConnectionFactory factory = ConnectionProvider.getConnectionFactory();
            final JvmConnection connection;
            try {
                connection = factory.createSocket("localhost", port);
                // The actual connection may be made some time from now,
                // so set up a listener to be notified at that time.
                connection.addConnectionListener(new ConnectionListener() {

                    @Override
                    public void connected(ConnectionEvent event) {
                        if (session.isConnected()) {
                            // The user already connected to something else.
                            JvmConnection c = event.getConnection();
                            c.getVM().dispose();
                            c.disconnect();
                        } else {
                            session.connect(connection);
                        }
                    }
                });
                connection.connect();
            } catch (Exception e) {
                logger.log(Level.SEVERE, null, e);
            }
        }
    }

    /**
     * Returns {@code true} if we are attempting to emulate JDB
     * enough to run within clients (such as Emacs) that may invoke
     * JDB as a subprocess and parse its output.
     *
     * @return  true if emulating JDB output, false otherwise.
     */
    public static boolean emulateJDB() {
        return jdbEmulationMode;
    }
}