import javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.awt.event.*; import java.io.BufferedReader; import java.io.FileReader; import java.util.ArrayList; import java.util.concurrent.Semaphore; public class WebFrame extends JFrame { public static final int DEFAULT_NUM_WORKERS = 4; public static final int URL_COL = 0; public static final int STAT_COL = 1; // I ended up retyping the strings so often that this was the best solution public static final String RUNNING_LABEL = "Running:"; public static final String COMPLETED_LABEL = "Completed:"; public static final String ELAPSED_LABEL = "Elapsed:"; private DefaultTableModel model; private JTable table; private JButton singleFetchButton; private JButton concurrentFetchButton; private JButton stopButton; private JTextField numThreadsField; private JLabel runningLabel; private JLabel completedLabel; private JLabel elapsedLabel; private JProgressBar progressBar; private Launcher launcher; private int numRunningWorkers; private int numCompleted; private Object workerCountLock; private Object workerCreateLock; private Semaphore workerLimitLock; private long startTime; private ArrayList<Thread> workers; public WebFrame() { this("links.txt"); } /* * constructor */ public WebFrame(String file) { workerCountLock = new Object(); workerCreateLock = new Object(); numRunningWorkers = 0; numCompleted = 0; workers = new ArrayList<Thread>(); setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); setUpTable(file); setUpStartButtons(); setUpFieldsAndLabels(); setUpProgressBar(); setUpStopButton(); pack(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } // alters the numRunningWorkers variable but the passed value // also updates the interface public void changeWorkerCount(int val) { synchronized(workerCountLock) { numRunningWorkers += val; if(val < 0) numCompleted++; } SwingUtilities.invokeLater(new Runnable() { public void run() { runningLabel.setText(RUNNING_LABEL + numRunningWorkers); completedLabel.setText(COMPLETED_LABEL + numCompleted); if(numRunningWorkers == 0) { setReadyState(); elapsedLabel.setText(ELAPSED_LABEL + (System.currentTimeMillis() - startTime) / 1000.0); } } }); } // method invoked by a worker thread when it is finished public void sendCompletionNotice() { changeWorkerCount(-1); workerLimitLock.release(); SwingUtilities.invokeLater(new Runnable() { public void run() { progressBar.setValue(progressBar.getValue() + 1); } }); } // method invoked by a worker thread to update a particular // row in the table with the passed message public void updateRow(final int row, final String msg) { SwingUtilities.invokeLater(new Runnable() { public void run() { model.setValueAt(msg, row, STAT_COL); } }); } public static void main(String[] args) { JFrame webFrame = new WebFrame(); } // ------------- Private ------------- // // sets up the table private void setUpTable(String file) { model = new DefaultTableModel(new String[] {"url", "status"}, 0); parseURLs(file); table = new JTable(model); table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); JScrollPane scrollpane = new JScrollPane(table); scrollpane.setPreferredSize(new Dimension(600, 300)); add(scrollpane); } // sets up the start buttons private void setUpStartButtons() { singleFetchButton = new JButton("Single Thread Fetch"); singleFetchButton.setAlignmentX(Component.LEFT_ALIGNMENT); singleFetchButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { startLauncher(1); } }); concurrentFetchButton = new JButton("Concurrent Fetch"); concurrentFetchButton.setAlignmentX(Component.LEFT_ALIGNMENT); concurrentFetchButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { startLauncher(Math.max(1, Integer.parseInt(numThreadsField.getText()))); } }); add(singleFetchButton); add(concurrentFetchButton); } // sets up the text field, labels private void setUpFieldsAndLabels() { numThreadsField = new JTextField(Integer.toString(DEFAULT_NUM_WORKERS)); numThreadsField.setMaximumSize(new Dimension(100, numThreadsField.getHeight())); numThreadsField.setAlignmentX(Component.LEFT_ALIGNMENT); runningLabel = new JLabel(RUNNING_LABEL); runningLabel.setAlignmentX(Component.LEFT_ALIGNMENT); completedLabel = new JLabel(COMPLETED_LABEL); completedLabel.setAlignmentX(Component.LEFT_ALIGNMENT); elapsedLabel = new JLabel(ELAPSED_LABEL); elapsedLabel.setAlignmentX(Component.LEFT_ALIGNMENT); add(numThreadsField); add(runningLabel); add(completedLabel); add(elapsedLabel); } // sets up the progress bar private void setUpProgressBar() { progressBar = new JProgressBar(0, 10); progressBar.setValue(0); progressBar.setStringPainted(true); add(progressBar); } // sets up the stop button private void setUpStopButton() { stopButton = new JButton("Stop"); stopButton.setEnabled(false); stopButton.setAlignmentX(Component.LEFT_ALIGNMENT); stopButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { synchronized(workerCreateLock) { if(launcher != null) { launcher.interrupt(); launcher = null; } if(!workers.isEmpty()) for (Thread worker : workers) worker.interrupt(); } } }); add(stopButton); } // parse URLs from file private void parseURLs(String file) { try { BufferedReader reader = new BufferedReader(new FileReader(file)); String line; while((line = reader.readLine()) != null) model.addRow(new String[] {line, ""}); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } // starts the launcher thread private void startLauncher(int numWorkers) { resetInterface(); workerLimitLock = new Semaphore(numWorkers); launcher = new Launcher(this); startTime = System.currentTimeMillis(); launcher.start(); setRunningState(); } // resets the interface private void resetInterface() { numCompleted = 0; progressBar.setValue(0); progressBar.setMaximum(model.getRowCount()); runningLabel.setText(RUNNING_LABEL); completedLabel.setText(COMPLETED_LABEL); elapsedLabel.setText(ELAPSED_LABEL); for(int i = 0; i < model.getRowCount(); i++) { model.setValueAt("", i, STAT_COL); } } // sets the ready state for the GUI private void setReadyState() { singleFetchButton.setEnabled(true); concurrentFetchButton.setEnabled(true); stopButton.setEnabled(false); numThreadsField.setEnabled(true); } // sets the running state of the GUI private void setRunningState() { singleFetchButton.setEnabled(false); concurrentFetchButton.setEnabled(false); stopButton.setEnabled(true); numThreadsField.setEnabled(false); } /* * inner Launcher class used to spawn off workers */ private class Launcher extends Thread { private WebFrame f; public Launcher(WebFrame f) { this.f = f; } public void run() { changeWorkerCount(1); int numURLs = model.getRowCount(); workers.clear(); for(int i = 0; i < numURLs; i++) { try { workerLimitLock.acquire(); } catch (InterruptedException e) { break; } synchronized(workerCreateLock) { Thread worker = new WebWorker((String) model.getValueAt(i, 0), i, f); workers.add(worker); worker.start(); } } changeWorkerCount(-1); } } }