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. } }