Java tutorial
/* Java Threads, 3rd Edition By Scott Oaks, Henry Wong 3rd Edition September 2004 ISBN: 0-596-00782-5 */ import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.lang.reflect.InvocationTargetException; import java.util.Random; import java.util.Vector; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; public class SwingTypeTester9 extends JFrame implements CharacterSource { protected RandomCharacterGenerator producer; private AnimatedCharacterDisplayCanvas displayCanvas; private CharacterDisplayCanvas feedbackCanvas; private JButton quitButton; private JButton startButton; private JButton stopButton; private CharacterEventHandler handler; private ScoreLabel score; private SwingTypeTester9 parent; public SwingTypeTester9() { initComponents(); } private void initComponents() { parent = this; handler = new CharacterEventHandler(); producer = new RandomCharacterGenerator(); producer.setDone(true); producer.start(); displayCanvas = new AnimatedCharacterDisplayCanvas(producer); feedbackCanvas = new CharacterDisplayCanvas(this); quitButton = new JButton(); startButton = new JButton(); stopButton = new JButton(); score = new ScoreLabel(producer, this); Container pane = getContentPane(); JPanel p1 = new JPanel(); p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS)); p1.add(displayCanvas); p1.add(feedbackCanvas); JPanel p2 = new JPanel(); score.setText(" "); score.setFont(new Font("MONOSPACED", Font.BOLD, 30)); p2.add(score); startButton.setText("Start"); p2.add(startButton); stopButton.setText("Stop"); stopButton.setEnabled(false); p2.add(stopButton); quitButton.setText("Quit"); p2.add(quitButton); p1.add(p2); pane.add(p1, BorderLayout.NORTH); pack(); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { quit(); } }); feedbackCanvas.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent ke) { char c = ke.getKeyChar(); if (c != KeyEvent.CHAR_UNDEFINED) newCharacter((int) c); } }); startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { new FeedbackFrame(parent).show(); } }); stopButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { startButton.setEnabled(true); stopButton.setEnabled(false); producer.setDone(true); displayCanvas.setDone(true); feedbackCanvas.setEnabled(false); } }); quitButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { quit(); } }); } void setupDone() { displayCanvas.setDone(false); producer.setDone(false); score.resetScore(); startButton.setEnabled(false); stopButton.setEnabled(true); feedbackCanvas.setEnabled(true); feedbackCanvas.requestFocus(); } void setupCancelled() { } private void quit() { System.exit(0); } public void addCharacterListener(CharacterListener cl) { handler.addCharacterListener(cl); } public void removeCharacterListener(CharacterListener cl) { handler.removeCharacterListener(cl); } public void newCharacter(int c) { handler.fireNewCharacter(this, c); } public void nextCharacter() { throw new IllegalStateException("We don't produce on demand"); } public static void main(String args[]) { new SwingTypeTester9().show(); } } class RandomCharacterGenerator extends Thread implements CharacterSource { private static char[] chars; private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789"; static { chars = charArray.toCharArray(); } private Random random; private CharacterEventHandler handler; private boolean done = true; private Lock lock = new ReentrantLock(); private Condition cv = lock.newCondition(); public RandomCharacterGenerator() { random = new Random(); handler = new CharacterEventHandler(); } public int getPauseTime(int minTime, int maxTime) { return (int) (minTime + ((maxTime - minTime) * random.nextDouble())); } public int getPauseTime() { return getPauseTime(2000, 5500); } public void addCharacterListener(CharacterListener cl) { handler.addCharacterListener(cl); } public void removeCharacterListener(CharacterListener cl) { handler.removeCharacterListener(cl); } public void nextCharacter() { handler.fireNewCharacter(this, (int) chars[random.nextInt(chars.length)]); } public void run() { try { lock.lock(); while (true) { try { if (done) { cv.await(); } else { nextCharacter(); cv.await(getPauseTime(), TimeUnit.MILLISECONDS); } } catch (InterruptedException ie) { return; } } } finally { lock.unlock(); } } public void setDone(boolean b) { try { lock.lock(); done = b; if (!done) cv.signal(); } finally { lock.unlock(); } } } class FeedbackFrame extends JFrame implements Runnable { private SwingTypeTester9 stt; private Thread t; private JLabel label; private int state; static String[] stateMessages = { "Connecting to server...", "Logging into server...", "Waiting for data...", "Complete" }; public FeedbackFrame(SwingTypeTester9 stt) { this.stt = stt; setupFrame(); t = new Thread(this); t.start(); pack(); show(); } private void setupFrame() { label = new JLabel(); label.setPreferredSize(new Dimension(200, 200)); Container c = getContentPane(); JButton stopButton = new JButton("Stop"); stopButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { error(); } }); c.add(label, BorderLayout.NORTH); c.add(stopButton, BorderLayout.SOUTH); } private void setText(final String s) { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { label.setText(s); } }); } catch (InterruptedException ie) { error(); } catch (InvocationTargetException ite) { error(); } } private void error() { t.interrupt(); if (SwingUtilities.isEventDispatchThread()) closeDown(); else SwingUtilities.invokeLater(new Runnable() { public void run() { closeDown(); } }); } private void closeDown() { stt.setupCancelled(); hide(); dispose(); } public void run() { // Simulate connecting to server for (int i = 0; i < stateMessages.length; i++) { setText(stateMessages[i]); try { Thread.sleep(5 * 1000); } catch (InterruptedException ie) { } if (Thread.currentThread().isInterrupted()) return; } SwingUtilities.invokeLater(new Runnable() { public void run() { stt.setupDone(); hide(); dispose(); } }); } } class ScoreLabel extends JLabel implements CharacterListener { private volatile int score = 0; private int char2type = -1; private CharacterSource generator = null, typist = null; private Lock scoreLock = new ReentrantLock(); public ScoreLabel(CharacterSource generator, CharacterSource typist) { this.generator = generator; this.typist = typist; if (generator != null) generator.addCharacterListener(this); if (typist != null) typist.addCharacterListener(this); } public ScoreLabel() { this(null, null); } public void resetGenerator(CharacterSource newGenerator) { try { scoreLock.lock(); if (generator != null) generator.removeCharacterListener(this); generator = newGenerator; if (generator != null) generator.addCharacterListener(this); } finally { scoreLock.unlock(); } } public void resetTypist(CharacterSource newTypist) { try { scoreLock.lock(); if (typist != null) typist.removeCharacterListener(this); typist = newTypist; if (typist != null) typist.addCharacterListener(this); } finally { scoreLock.unlock(); } } public void resetScore() { try { scoreLock.lock(); score = 0; char2type = -1; setScore(); } finally { scoreLock.unlock(); } } private void setScore() { SwingUtilities.invokeLater(new Runnable() { public void run() { setText(Integer.toString(score)); } }); } public void newCharacter(CharacterEvent ce) { scoreLock.lock(); try { if (ce.source == generator) { if (char2type != -1) { score--; setScore(); } char2type = ce.character; } else { if (char2type != ce.character) { score--; } else { score++; char2type = -1; } } setScore(); } finally { scoreLock.unlock(); } } } class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas implements CharacterListener, Runnable { private volatile boolean done = false; private int curX; private Lock lock = new ReentrantLock(); private Condition cv = lock.newCondition(); private Thread timer = null; public AnimatedCharacterDisplayCanvas(CharacterSource cs) { super(cs); } public synchronized void newCharacter(CharacterEvent ce) { curX = 0; tmpChar[0] = (char) ce.character; repaint(); } public synchronized void paintComponent(Graphics gc) { if (tmpChar[0] == 0) return; Dimension d = getSize(); int charWidth = fm.charWidth(tmpChar[0]); gc.clearRect(0, 0, d.width, d.height); gc.drawChars(tmpChar, 0, 1, curX++, fontHeight); if (curX > d.width - charWidth) curX = 0; } public void run() { try { lock.lock(); while (true) { try { if (done) { cv.await(); } else { repaint(); cv.await(100, TimeUnit.MILLISECONDS); } } catch (InterruptedException ie) { return; } } } finally { lock.unlock(); } } public void setDone(boolean b) { try { lock.lock(); done = b; if (timer == null) { timer = new Thread(this); timer.start(); } if (!done) cv.signal(); } finally { lock.unlock(); } } } interface CharacterSource { public void addCharacterListener(CharacterListener cl); public void removeCharacterListener(CharacterListener cl); public void nextCharacter(); } class CharacterEvent { public CharacterSource source; public int character; public CharacterEvent(CharacterSource cs, int c) { source = cs; character = c; } } class CharacterEventHandler { private Vector listeners = new Vector(); public void addCharacterListener(CharacterListener cl) { listeners.add(cl); } public void removeCharacterListener(CharacterListener cl) { listeners.remove(cl); } public void fireNewCharacter(CharacterSource source, int c) { CharacterEvent ce = new CharacterEvent(source, c); CharacterListener[] cl = (CharacterListener[]) listeners.toArray(new CharacterListener[0]); for (int i = 0; i < cl.length; i++) cl[i].newCharacter(ce); } } class CharacterDisplayCanvas extends JComponent implements CharacterListener { protected FontMetrics fm; protected char[] tmpChar = new char[1]; protected int fontHeight; public CharacterDisplayCanvas(CharacterSource cs) { setFont(new Font("Monospaced", Font.BOLD, 18)); fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont()); fontHeight = fm.getHeight(); cs.addCharacterListener(this); } public void setCharacterListener(CharacterSource cs) { cs.addCharacterListener(this); } public Dimension preferredSize() { return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10); } public synchronized void newCharacter(CharacterEvent ce) { tmpChar[0] = (char) ce.character; repaint(); } protected synchronized void paintComponent(Graphics gc) { Dimension d = getSize(); gc.clearRect(0, 0, d.width, d.height); if (tmpChar[0] == 0) return; int charWidth = fm.charWidth((int) tmpChar[0]); gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight); } } interface CharacterListener { public void newCharacter(CharacterEvent ce); }