4 package net
.kezvh
.toady
;
6 import java
.awt
.event
.ActionEvent
;
7 import java
.awt
.event
.ActionListener
;
8 import java
.awt
.event
.KeyEvent
;
9 import java
.awt
.event
.KeyListener
;
10 import java
.awt
.event
.MouseEvent
;
11 import java
.awt
.event
.MouseListener
;
13 import java
.io
.FileInputStream
;
14 import java
.io
.FileWriter
;
15 import java
.io
.IOException
;
16 import java
.io
.InputStreamReader
;
17 import java
.io
.LineNumberReader
;
18 import java
.util
.Iterator
;
19 import java
.util
.concurrent
.locks
.ReentrantLock
;
20 import java
.util
.regex
.Matcher
;
21 import java
.util
.regex
.Pattern
;
23 import javax
.swing
.JFileChooser
;
24 import javax
.swing
.JFrame
;
25 import javax
.swing
.JMenuBar
;
26 import javax
.swing
.JOptionPane
;
27 import javax
.swing
.JScrollPane
;
28 import javax
.swing
.JTextArea
;
29 import javax
.swing
.SwingUtilities
;
31 import net
.kezvh
.collections
.trees
.ListRefTree
;
32 import net
.kezvh
.collections
.trees
.Tree
;
33 import net
.kezvh
.development
.UnimplementedException
;
34 import net
.kezvh
.ui
.menu
.SpringMenu
;
36 import org
.apache
.commons
.lang
.StringEscapeUtils
;
37 import org
.apache
.commons
.lang
.exception
.ExceptionUtils
;
45 private static StringBuilder contents
= new StringBuilder();
46 private static final JTextArea textArea
= new JTextArea(5, 20);
47 private static long lastChangeTime
= System
.nanoTime();
49 private static final ReentrantLock lock
= new ReentrantLock();
51 private static final Object MENU
= new Object
[] { "File", new Object
[] { "New", new Object
[] { KeyEvent
.VK_N
, NewActionListener
.class }, "Open", new Object
[] { KeyEvent
.VK_O
, OpenActionListener
.class }, "Save", new Object
[] { KeyEvent
.VK_S
, SaveActionListener
.class, }, }, "Edit", new Object
[] { "Undo", new Object
[] { KeyEvent
.VK_Z
, UndoActionListener
.class, }, "Replay", new Object
[] { KeyEvent
.VK_R
, ReplayActionListener
.class, }, "Reset Timer", new Object
[] { KeyEvent
.VK_U
, ResetActionListener
.class, }, }, };
53 private static final Tree
<EditToadyAction
> sequenceOfEdits
= new ListRefTree
<EditToadyAction
>(new EditToadyAction("", "", 0, 0));
54 private static Tree
<EditToadyAction
> currentAction
= Toady2
.sequenceOfEdits
;
55 private static final JFrame frame
= new JFrame("Toady");
57 private static boolean saved
= true;
63 private static final class TodayTextMouseListener
implements MouseListener
{
65 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
69 public void mouseClicked(final MouseEvent e
) {
74 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
78 public void mouseEntered(final MouseEvent e
) {
83 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
87 public void mouseExited(final MouseEvent e
) {
93 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
97 public void mousePressed(final MouseEvent e
) {
102 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
106 public void mouseReleased(final MouseEvent e
) {
115 private static final class ToadyTextKeyListener
implements KeyListener
{
117 * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
121 public void keyPressed(final KeyEvent e
) {
126 * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
130 public void keyReleased(final KeyEvent e
) {
135 * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
139 public void keyTyped(final KeyEvent e
) {
148 public static final class ReplayActionListener
implements ActionListener
{
150 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
154 public void actionPerformed(final ActionEvent e
) {
155 synchronized (Toady2
.lock
) {
158 final StringBuilder currentContents
= new StringBuilder();
159 Toady2
.textArea
.setEditable(false);
160 Toady2
.textArea
.setText("");
162 final Iterator
<EditToadyAction
> iterator
= Toady2
.sequenceOfEdits
.iterator();
163 Toady2
.timedReplay(iterator
, currentContents
);
171 public static final class ResetActionListener
implements ActionListener
{
173 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
177 public void actionPerformed(final ActionEvent e
) {
178 Toady2
.lastChangeTime
= System
.nanoTime();
186 public static final class UndoActionListener
implements ActionListener
{
188 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
192 public void actionPerformed(final ActionEvent e
) {
194 Toady2
.lastChangeTime
= System
.nanoTime();
198 private static void rewind() {
199 if (Toady2
.sequenceOfEdits
.size() == 0)
201 final EditToadyAction last
= Toady2
.sequenceOfEdits
.remove(Toady2
.sequenceOfEdits
.size() - 1);
202 System
.out
.println(last
);
203 Toady2
.contents
.replace(last
.getStartPos(), last
.getEndPos() + last
.getEditContents().length(), last
.getPreviousContents());
204 Toady2
.textArea
.setText(Toady2
.contents
.toString());
211 public static final class OpenActionListener
implements ActionListener
{
213 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
217 public void actionPerformed(final ActionEvent e
) {
218 final JFileChooser chooser
= new JFileChooser();
220 // if you have a suggested file name to start with, use that
222 final int response
= chooser
.showOpenDialog(Toady2
.frame
);
223 if (response
== JFileChooser
.APPROVE_OPTION
) {// user clicked Save
224 final File file
= chooser
.getSelectedFile();
226 final LineNumberReader lineNumberReader
= new LineNumberReader(new InputStreamReader(new FileInputStream(file
)));
228 Toady2
.sequenceOfEdits
.clear();
229 for (String line
= lineNumberReader
.readLine(); line
!= null; line
= lineNumberReader
.readLine())
230 if (line
.startsWith("<ToadyAction>"))
231 Toady2
.sequenceOfEdits
.add(new EditToadyAction(line
));
233 Toady2
.contents
= new StringBuilder(StringEscapeUtils
.unescapeJava(line
));
234 Toady2
.textArea
.setText(Toady2
.contents
.toString());
237 } catch (final IOException e1
) {
238 // TODO Auto-generated catch block
239 e1
.printStackTrace();
242 if (!Toady2
.contents
.equals(Toady2
.replay()))
243 throw new RuntimeException(Toady2
.replay());
255 public static final class SaveActionListener
implements ActionListener
{
257 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
261 public void actionPerformed(final ActionEvent e
) {
270 public static final class NewActionListener
implements ActionListener
{
272 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
276 public void actionPerformed(final ActionEvent e
) {
277 if (!Toady2
.saved
&& Toady2
.promptToSave() && !Toady2
.save())
280 Toady2
.contents
.delete(0, Toady2
.contents
.length());
281 Toady2
.textArea
.setText("");
282 Toady2
.sequenceOfEdits
.clear();
286 private static boolean promptToSave() {
287 throw new UnimplementedException();
291 * @return true if the file is sucessfully saved
293 private static boolean save() {
294 final JFileChooser chooser
= new JFileChooser();
296 // if you have a suggested file name to start with, use that
298 final int response
= chooser
.showSaveDialog(Toady2
.frame
);
299 if (response
== JFileChooser
.APPROVE_OPTION
) {// user clicked Save
300 final File file
= chooser
.getSelectedFile();
302 final FileWriter fileWriter
= new FileWriter(file
);
303 for (final EditToadyAction toadyAction
: Toady2
.sequenceOfEdits
)
304 fileWriter
.write(toadyAction
.toString() + "\n");
305 fileWriter
.write(StringEscapeUtils
.escapeJava(Toady2
.contents
.toString()));
306 fileWriter
.write("\n");
309 } catch (final IOException e1
) {
310 e1
.printStackTrace(); // TODO do something with this
319 private interface ToadyAction
{
323 private static EditToadyAction
diff(final String newString
) {
327 while (newString
.length() != startPos
&& Toady2
.contents
.length() != startPos
&& Toady2
.contents
.charAt(startPos
) == newString
.charAt(startPos
))
330 while (newString
.length() != startPos
+ endPos
&& Toady2
.contents
.length() != startPos
+ endPos
&& Toady2
.contents
.charAt(Toady2
.contents
.length() - 1 - endPos
) == newString
.charAt(newString
.length() - 1 - endPos
))
333 if (Toady2
.contents
.length() == newString
.length() && startPos
+ endPos
== newString
.length())
334 return null; // no change
336 if (newString
.length() - endPos
< 0)
337 throw new RuntimeException("error; end pos = " + endPos
+ " and string is " + newString
.length());
339 if (Toady2
.contents
.length() - endPos
< 0)
340 throw new RuntimeException("end position precedes beginning of string: " + endPos
+ ", " + Toady2
.contents
.length());
343 throw new RuntimeException("??????");
345 if (newString
.equals(Toady2
.contents
) && startPos
!= endPos
)
346 throw new RuntimeException(newString
+ ": " + startPos
+ ", " + endPos
);
348 if (startPos
> newString
.length() - endPos
)
349 throw new RuntimeException(startPos
+ ", " + (newString
.length() - endPos
) + " : " + Toady2
.contents
+ " - " + newString
);
352 final long now
= System
.nanoTime();
353 if (Toady2
.lastChangeTime
== -1)
356 duration
= System
.nanoTime() - Toady2
.lastChangeTime
;
357 Toady2
.lastChangeTime
= now
;
358 return new EditToadyAction(newString
.substring(startPos
, newString
.length() - endPos
), Toady2
.contents
.substring(startPos
, Toady2
.contents
.length() - endPos
), startPos
, Toady2
.contents
.length() - endPos
, duration
);
361 private static final class EditToadyAction
implements ToadyAction
{
363 * @return the previousContents
365 public String
getPreviousContents() {
366 return this.previousContents
;
370 * @return the duration
372 public long getDuration() {
373 return this.duration
;
377 * @return the editContents
379 public String
getEditContents() {
380 return this.editContents
;
384 * @return the startPos
386 public int getStartPos() {
387 return this.startPos
;
391 * @param editContents
392 * @param previousContents
396 public EditToadyAction(final String editContents
, final String previousContents
, final int startPos
, final long duration
) {
398 this.editContents
= editContents
;
399 this.previousContents
= previousContents
;
400 this.startPos
= startPos
;
401 this.duration
= duration
;
404 private final Pattern p
= Pattern
.compile("<ToadyAction><start>(.*)</start><contents>(.*)</contents><previous>(.*)</previous><duration>(.*)</duration></ToadyAction>");
406 public EditToadyAction(final String s
) {
407 final Matcher m
= this.p
.matcher(s
);
409 throw new RuntimeException();
410 this.startPos
= Integer
.valueOf(m
.group(1));
411 this.editContents
= StringEscapeUtils
.unescapeJava(StringEscapeUtils
.unescapeXml(m
.group(2)));
412 this.previousContents
= StringEscapeUtils
.unescapeJava(StringEscapeUtils
.unescapeXml(m
.group(3)));
413 this.duration
= Long
.valueOf(m
.group(4));
417 private final String editContents
;
418 private final String previousContents
;
419 private final int startPos
;
420 private final long duration
;
423 * @see java.lang.Object#toString()
427 public String
toString() {
428 return "<ToadyAction><start>" + this.startPos
+ "</start><contents>" + StringEscapeUtils
.escapeJava(StringEscapeUtils
.escapeXml(this.editContents
)) + "</contents>" + StringEscapeUtils
.escapeJava(StringEscapeUtils
.escapeXml(this.previousContents
)) + "<previous><duration>" + this.duration
+ "</duration></ToadyAction>";
435 public static void main(final String
[] args
) {
436 System
.setProperty("sun.awt.exception.handler", AwtHandler
.class.getName());
437 Thread
.setDefaultUncaughtExceptionHandler(new MyExceptionHandler());
439 final JMenuBar menuBar
= new SpringMenu(Toady2
.MENU
);
441 Toady2
.frame
.setJMenuBar(menuBar
);
443 Toady2
.textArea
.setLineWrap(true);
445 Toady2
.textArea
.addKeyListener(new ToadyTextKeyListener());
447 Toady2
.textArea
.addMouseListener(new TodayTextMouseListener());
449 final JScrollPane scrollPane
= new JScrollPane(Toady2
.textArea
);
451 Toady2
.frame
.getContentPane().add(scrollPane
);
453 Toady2
.frame
.setDefaultCloseOperation(JFrame
.EXIT_ON_CLOSE
);
455 Toady2
.frame
.setVisible(true);
458 private static void timedReplay(final Iterator
<EditToadyAction
> iterator
, final StringBuilder currentContents
) {
459 SwingUtilities
.invokeLater(new Runnable() {
461 if (iterator
.hasNext())
462 synchronized (Toady2
.sequenceOfEdits
) {
463 final EditToadyAction editToadyAction
= iterator
.next();
465 final long duration
= editToadyAction
.getDuration() / 10;
467 Toady2
.sequenceOfEdits
.wait(0, 1);
469 final long milis
= duration
/ 1000000;
470 final int nanos
= (int) (duration
% 1000000);
471 Toady2
.sequenceOfEdits
.wait(milis
, nanos
);
474 } catch (final InterruptedException e1
) {
475 // nothing should interrupt us
477 currentContents
.replace(editToadyAction
.getStartPos(), editToadyAction
.getEndPos(), editToadyAction
.getEditContents());
478 Toady2
.textArea
.setText(currentContents
.toString());
479 Toady2
.timedReplay(iterator
, currentContents
);
482 if (!Toady2
.contents
.toString().equals(currentContents
.toString()))
483 throw new RuntimeException("NOT EQUAL\n'" + Toady2
.contents
+ "'\nvs\n'" + currentContents
+ "'");
485 synchronized (Toady2
.lock
) {
486 Toady2
.lock
.unlock();
489 Toady2
.textArea
.setEditable(true);
490 Toady2
.lastChangeTime
= System
.nanoTime();
498 private static void update() {
499 synchronized (Toady2
.lock
) {
500 if (Toady2
.lock
.isHeldByCurrentThread())
501 return; // just give up
504 final String newText
= Toady2
.textArea
.getText();
506 final EditToadyAction editToadyAction
= Toady2
.diff(newText
);
507 if (editToadyAction
== null)
510 Toady2
.saved
= false;
512 Toady2
.contents
.delete(0, Toady2
.contents
.length());
513 Toady2
.contents
.append(newText
);
515 Toady2
.sequenceOfEdits
.add(editToadyAction
);
519 private static class AwtHandler
{
520 public void handle(final Throwable t
) {
521 JOptionPane
.showMessageDialog(null, ExceptionUtils
.getStackTrace(t
), "Error", 1);
525 private static class MyExceptionHandler
implements Thread
.UncaughtExceptionHandler
{
527 public void uncaughtException(final Thread t
, final Throwable e
) {
528 if (SwingUtilities
.isEventDispatchThread())
529 this.showException(t
, e
);
531 SwingUtilities
.invokeLater(new Runnable() {
533 MyExceptionHandler
.this.showException(t
, e
);
538 private void showException(final Thread t
, final Throwable e
) {
539 final String msg
= String
.format("Unexpected problem on thread %s: %s\n%s", t
.getName(), e
.getMessage(), ExceptionUtils
.getStackTrace(e
));
541 this.logException(t
, e
);
543 // note: in a real app, you should locate the currently focused frame
544 // or dialog and use it as the parent. In this example, I'm just passing
545 // a null owner, which means this dialog may get buried behind
546 // some other screen.
547 JOptionPane
.showMessageDialog(null, msg
);
550 private void logException(final Thread t
, final Throwable e
) {
551 // todo: start a thread that sends an email, or write to a log file, or
552 // send a JMS message...whatever
556 private static String
replay() {
557 final StringBuilder sb
= new StringBuilder();
558 for (final EditToadyAction editToadyAction
: Toady2
.sequenceOfEdits
)
559 sb
.replace(editToadyAction
.getStartPos(), editToadyAction
.getEndPos(), editToadyAction
.getEditContents());
560 return sb
.toString();