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