SpanningTreeApplet.java

package edu.hawaii.ics.yucheng;

import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessControlException;
import java.util.Scanner;

import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

/**
 * An Applet that solves weighted graph spanning trees.
 */
@SuppressWarnings("serial")
public class SpanningTreeApplet extends JApplet {

    /**
     * A simple class that handles action on the download button.
     */
    class DownloadButtonDelegate implements ActionListener {
        /**
         * Executes when the download button is clicked.
         * 
         * @param event The action event (not used)
         */
        public void actionPerformed(final ActionEvent event) {
            assert null != myUrlField;
            assert null != myDownloadButton;
            assert null != myUrlField;
            assert null != myGraphBox;

            // Get the entered URL, checking for errors.
            final String text = myUrlField.getText();
            assert null != text;

            if (text.trim().length() == 0) {
                beep();
                myStatusLabel.setText("Please specify a URL before downloading",
                    STATUS_TIMEOUT);
                return;
            }

            final URL url;
            try {
                url = new URL(text);
            } catch (final MalformedURLException e) {
                beep();
                myStatusLabel.setText("Syntactically wrong string (not URL)",
                    STATUS_TIMEOUT);
                return;
            }

            // Disable the user interface.
            myDownloadButton.setEnabled(false);
            myRandomizeButton.setEnabled(false);
            myUrlField.setEnabled(false);

            // Start the background worker to download and solve the graph.
            final Worker worker = new DownloadGraphWorker(url);
            worker.start();
        }
    }

    /**
     * A class that solves downloaded graphs in a background thread.
     */
    class DownloadGraphWorker extends Worker {
        private final URL myUrl;


        /**
         * Initializes a new instance of the class. The class will download and
         * solve the graph at the specified URL if it executes.
         * 
         * @param url The URL.
         */
        public DownloadGraphWorker(final URL url) {
            assert url != null;
            myUrl = url;
        }


        /**
         * Solves a graph and returns the solution.
         * 
         * @param solver The graph solver.
         * 
         * @return The solution.
         * 
         * @throws Exception Thrown if there are any errors of any kind.
         */
        @Override
        protected GraphSolution solve(final GraphSolver solver) throws Exception {

            sendProgress("Connecting to " + myUrl + "...");
            try {
                final InputStream stream = myUrl.openStream();
                final Scanner scanner = new Scanner(stream);

                // The scanner can be passed directly into the Graph constructor, but
                // here we perform additional error handling. Because this is done
                // only to display more meaningful messages to the user, this is done
                // in this UI code, not in the Graph class.
                final StringBuilder builder = new StringBuilder();
                while (scanner.hasNextLine())
                    builder.append(scanner.nextLine() + '\n');
                final String content = builder.toString();
                for (int i = 0; i < content.length(); i++) {
                    final char ch = content.charAt(i);
                    if (Character.isDigit(ch) || " \t\n,.".indexOf(ch) >= 0)
                        continue;
                    if (ch < 32 || ch > 127)
                        throw new GraphException("Non-ASCII file detected");
                    throw new GraphException("Invalid character '" + ch + "' detected");
                }

                sendProgress("Reading graph...");
                final Graph graph = new Graph(new Scanner(content), myUrl);
                sendProgress(graph);

                // Animate small graphs.
                if (graph.vertices() <= MAX_ANIMATED_VERTICES) {
                    solver.startAnimation(ANIMATION_RATE);
                    sendProgress("Animating solution to small graph...");
                } else
                    sendProgress("Solving graph (minimizing weight)...");

                return solver.solve(graph);

            } catch (final GraphException e) {
                throw e;

            } catch (final FileNotFoundException e) {
                throw new GraphException("File doesn't exist at the URL (or denied)", e);

            } catch (final AccessControlException e) {
                throw new GraphException("File doesn't exist at the URL (or denied)", e);

            } catch (final Exception e) {
                throw new GraphException("Unable to read graph.", e);
            }
        }
    }

    /**
     * A class that solves random graphs in a background thread.
     */
    class RandomGraphWorker extends Worker {
        /**
         * Solves a graph and returns the solution.
         * 
         * @param solver The graph solver.
         * 
         * @return The solution.
         * 
         * @throws Exception Thrown if there are any errors of any kind.
         */
        @Override
        protected GraphSolution solve(final GraphSolver solver) throws Exception {

            sendProgress("Creating random graph...");
            final Graph graph = GraphRandomizer.create(2, 33);
            sendProgress(graph);

            // Animate small graphs.
            if (graph.vertices() <= MAX_ANIMATED_VERTICES) {
                solver.startAnimation(ANIMATION_RATE);
                sendProgress("Animating solution to small graph...");
            } else
                sendProgress("Solving graph (minimizing weight)...");

            return solver.solve(graph);
        }
    }

    /**
     * A simple class that handles action on the randomize button.
     */
    class RandomizeButtonDelegate implements ActionListener {
        /**
         * Executes when the randomize button is clicked.
         * 
         * @param e The action event (not used)
         */
        public void actionPerformed(final ActionEvent e) {
            assert null != myUrlField;
            assert null != myDownloadButton;
            assert null != myRandomizeButton;

            // Disable the user interface.
            myDownloadButton.setEnabled(false);
            myRandomizeButton.setEnabled(false);
            myUrlField.setEnabled(false);

            // Start a new worker thread.
            final Worker worker = new RandomGraphWorker();
            worker.start();
        }
    }

    /**
     * A class that downloads graphs and solves them in a background thread.
     */
    abstract class Worker extends BackgroundWorker<GraphSolution, Object> {

        /**
         * A simple class that listens for progress messages from the graph solver
         * and sends the progress data to the GUI thread.
         */
        class Delegate implements GraphListener {

            /**
             * Executes when progress arrives from the solver.
             * 
             * @param partialSolution A partial solution.
             */
            public void progress(final GraphSolution partialSolution) {
                // Send the partial solution to the user interface thread.
                sendProgress(partialSolution);
            }
        }


        @Override
        protected void processError(final Exception error) {
            assert null != myDownloadButton;
            assert null != myUrlField;
            assert null != myRandomizeButton;
            assert null != myStatusLabel;

            // Re-enable the user interface.
            myDownloadButton.setEnabled(true);
            myRandomizeButton.setEnabled(true);
            myUrlField.setEnabled(true);

            // Signal the error.
            beep();
            myStatusLabel.setText(error.getMessage(), STATUS_TIMEOUT);
            System.err.println(error);
        }


        /**
         * Executes when progress messages arrive from the background thread.
         * 
         * @param progress A list of messages that have arrived.
         */
        @Override
        protected void processProgress(final Object progress) {
            assert null != progress;
            assert null != myGraphBox;
            assert null != myStatusLabel;

            // Check if the progress message is a graph.
            if (progress instanceof Graph) {
                myGraphBox.setGraph((Graph) progress);
                return;
            }

            // Check if the progress message is a partial solution.
            if (progress instanceof GraphSolution) {
                myGraphBox.setSolution((GraphSolution) progress);
                return;
            }

            // Otherwise, this is a status message.
            if (progress instanceof String) {
                myStatusLabel.setText((String) progress);
                return;
            }

            assert false;
        }


        /**
         * Executes when the background thread finishes.
         */
        @Override
        protected void processResult(final GraphSolution result) {
            assert null != myDownloadButton;
            assert null != myUrlField;
            assert null != myGraphBox;
            assert null != myStatusLabel;

            // Re-enable the user interface.
            myDownloadButton.setEnabled(true);
            myRandomizeButton.setEnabled(true);
            myUrlField.setEnabled(true);

            // Get the solution and update the user interface.
            myGraphBox.setSolution(result);
            myStatusLabel.setText("Solved.", STATUS_TIMEOUT);
            myStatusLabel.setDefaultText("Ready to optimize more graphs...");
            return;
        }


        /**
         * The entry point for the background thread.
         * 
         * @return A solution for a graph.
         */
        @Override
        protected GraphSolution run() throws Exception {

            // Use the best solver we know of.
            final GraphSolver solver = new JadeSolver();

            // Monitor animation events from the solver.
            solver.addGraphListener(new Delegate());

            // Solve the graph and return the solution.
            return solve(solver);
        }


        /**
         * Solves a graph and returns the solution.
         * 
         * @param solver The graph solver.
         * 
         * @return The solution.
         * 
         * @throws Exception Thrown if there are any errors of any kind.
         */
        protected abstract GraphSolution solve(GraphSolver solver) throws Exception;
    }

    private static final int ANIMATION_RATE        = 250;

    private static final int MAX_ANIMATED_VERTICES = 6;


    /**
     * Beeps.
     */
    static void beep() {

        try {
            Toolkit.getDefaultToolkit().beep();

        } catch (final Exception e) {
            // Swallow exceptions here.
            System.err.println(e);
        }
    }

    /**
     * A component that allows the user to solve a downloaded graph.
     */
    JButton           myDownloadButton;

    /**
     * A component that displays the graph.
     */
    GraphBox          myGraphBox;

    /**
     * A component that allows the user to solve a randomized graph.
     */
    JButton           myRandomizeButton;

    /**
     * A component that displays status.
     */
    StatusLabel       myStatusLabel;

    /**
     * A component that displays the URL.
     */
    JTextField        myUrlField;

    private final int STATUS_TIMEOUT = 3000;


    /**
     * Initializes the applet.
     */
    @Override
    public void init() {
        // Make this applet look as native as possible.
        setLookAndFeel();
        Container pane = getContentPane();
        pane.setBackground(new Color(0xfa, 0xfa, 0xfa));

        // Use a custom layout manager to allow the control to be resized.
        final int cx = 10 + 30 + 10 + 10 + 100 + 10 + 100 + 10; // 280
        final int cy = 10 + 40 + 20 + 10 + 10 + 25 + 10; // 125
        setLayout(new AnchorLayoutManager(cx, cy));

        // Create the title message.
        final JLabel titleLabel = new JLabel("Weighted Graph Spanning Tree");
        titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 25));
        titleLabel.setBounds(10, 10, 260, 40);
        add(titleLabel, "TLR");

        // Create the status message.
        myStatusLabel = new StatusLabel("Ready to optimize graphs...");
        myStatusLabel.setDefaultText("Ready to optimize more graphs...");
        myStatusLabel.setBounds(10, 50, 260, 20);
        add(myStatusLabel, "TLR");

        // Create the graph box.
        myGraphBox = new GraphBox();
        myGraphBox.setBounds(10, 70, 260, 10);
        add(myGraphBox, "TLBR");

        // Create a URL: label.
        final JLabel urlLabel = new JLabel("URL:");
        urlLabel.setBounds(10, 90, 30, 25);
        add(urlLabel, "BL");

        // Determine the initial URL.
        boolean autoStart = false;
        String initial = "http://www2.hawaii.edu/~yucheng/coursework/ics-311/assignment-2/applet/sample.graph";
        String url = getDocumentBase().toString();
        int q = url.indexOf("?url=");
        if (q >= 0) {
            url = url.substring(q + 5);
            q = url.indexOf("&");
            if (q >= 0)
                url = url.substring(0, q);
            initial = url;
            autoStart = true;
        }

        // Create a URL text field.
        myUrlField = new JTextField(initial);
        myUrlField.setBounds(40, 90, 10, 25);
        add(myUrlField, "BLR");

        // Create a download button.
        myDownloadButton = new JButton("Download");
        myDownloadButton.setBounds(60, 90, 100, 25);
        final DownloadButtonDelegate delegate = new DownloadButtonDelegate();
        myDownloadButton.addActionListener(delegate);
        add(myDownloadButton, "BR");

        // Create a randomize button.
        myRandomizeButton = new JButton("Randomize");
        myRandomizeButton.setBounds(170, 90, 100, 25);
        myRandomizeButton.addActionListener(new RandomizeButtonDelegate());
        add(myRandomizeButton, "BR");

        // Automatically start when the URL is specified.
        if (autoStart)
            delegate.actionPerformed(null);
    }


    /**
     * Attempts to use the look and feel of the current system.
     */
    private void setLookAndFeel() {
        try {
            final String name = UIManager.getSystemLookAndFeelClassName();
            UIManager.setLookAndFeel(name);
            SwingUtilities.updateComponentTreeUI(this);
        } catch (final ClassNotFoundException e) {
            System.err.println(e);
            // Swallow exceptions here.
        } catch (final InstantiationException e) {
            System.err.println(e);
            // Swallow exceptions here.
        } catch (final IllegalAccessException e) {
            System.err.println(e);
            // Swallow exceptions here.
        } catch (final UnsupportedLookAndFeelException e) {
            System.err.println(e);
            // Swallow exceptions here.
        }
    }


    /**
     * Executes when the applet starts.
     */
    @Override
    public void start() {
        // Nothing to do here.
    }


    /**
     * Executes when the applet is stopped.
     */
    @Override
    public void stop() {
        // The background worker executes so quickly, it isn't worth canceling the
        // thread.
    }
}
Valid HTML 4.01 Valid CSS