a whole bunch of stuff
[ephemerata.git] / Toady / src / net / kezvh / toady / Toady2.java
blobb8eb8d0e7ca4cd947f819d95f76bd237ada62a28
1 /**
3 */
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;
12 import java.io.File;
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;
39 /**
40 * @author afflux
43 public class Toady2 {
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;
59 /**
60 * @author afflux
63 private static final class TodayTextMouseListener implements MouseListener {
64 /**
65 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
66 * @param e
68 @Override
69 public void mouseClicked(final MouseEvent e) {
70 Toady2.update();
73 /**
74 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
75 * @param e
77 @Override
78 public void mouseEntered(final MouseEvent e) {
79 // noop
82 /**
83 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
84 * @param e
86 @Override
87 public void mouseExited(final MouseEvent e) {
88 // noop
92 /**
93 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
94 * @param e
96 @Override
97 public void mousePressed(final MouseEvent e) {
98 Toady2.update();
102 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
103 * @param e
105 @Override
106 public void mouseReleased(final MouseEvent e) {
107 Toady2.update();
112 * @author afflux
115 private static final class ToadyTextKeyListener implements KeyListener {
117 * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
118 * @param e
120 @Override
121 public void keyPressed(final KeyEvent e) {
122 Toady2.update();
126 * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
127 * @param e
129 @Override
130 public void keyReleased(final KeyEvent e) {
131 Toady2.update();
135 * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
136 * @param e
138 @Override
139 public void keyTyped(final KeyEvent e) {
140 Toady2.update();
145 * @author afflux
148 public static final class ReplayActionListener implements ActionListener {
150 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
151 * @param e
153 @Override
154 public void actionPerformed(final ActionEvent e) {
155 synchronized (Toady2.lock) {
156 Toady2.lock.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);
168 * @author afflux
171 public static final class ResetActionListener implements ActionListener {
173 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
174 * @param e
176 @Override
177 public void actionPerformed(final ActionEvent e) {
178 Toady2.lastChangeTime = System.nanoTime();
183 * @author afflux
186 public static final class UndoActionListener implements ActionListener {
188 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
189 * @param e
191 @Override
192 public void actionPerformed(final ActionEvent e) {
193 Toady2.rewind();
194 Toady2.lastChangeTime = System.nanoTime();
198 private static void rewind() {
199 if (Toady2.sequenceOfEdits.size() == 0)
200 return;
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());
208 * @author afflux
211 public static final class OpenActionListener implements ActionListener {
213 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
214 * @param e
216 @Override
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();
225 try {
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));
232 else {
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());
245 } else {
246 // user cancelled...
252 * @author afflux
255 public static final class SaveActionListener implements ActionListener {
257 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
258 * @param e
260 @Override
261 public void actionPerformed(final ActionEvent e) {
262 Toady2.save();
267 * @author afflux
270 public static final class NewActionListener implements ActionListener {
272 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
273 * @param e
275 @Override
276 public void actionPerformed(final ActionEvent e) {
277 if (!Toady2.saved && Toady2.promptToSave() && !Toady2.save())
278 return;
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();
301 try {
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");
307 fileWriter.close();
309 } catch (final IOException e1) {
310 e1.printStackTrace(); // TODO do something with this
311 return false;
314 return true;
315 } else
316 return false;
319 private interface ToadyAction {
320 String toString();
323 private static EditToadyAction diff(final String newString) {
324 int startPos = 0;
325 int endPos = 0;
327 while (newString.length() != startPos && Toady2.contents.length() != startPos && Toady2.contents.charAt(startPos) == newString.charAt(startPos))
328 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))
331 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());
342 if (startPos < 0)
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);
351 final long duration;
352 final long now = System.nanoTime();
353 if (Toady2.lastChangeTime == -1)
354 duration = 0;
355 else
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
393 * @param startPos
394 * @param duration
396 public EditToadyAction(final String editContents, final String previousContents, final int startPos, final long duration) {
397 super();
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);
408 if (!m.matches())
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()
424 * @return x
426 @Override
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>";
433 * @param args
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);
454 Toady2.frame.pack();
455 Toady2.frame.setVisible(true);
458 private static void timedReplay(final Iterator<EditToadyAction> iterator, final StringBuilder currentContents) {
459 SwingUtilities.invokeLater(new Runnable() {
460 public void run() {
461 if (iterator.hasNext())
462 synchronized (Toady2.sequenceOfEdits) {
463 final EditToadyAction editToadyAction = iterator.next();
464 try {
465 final long duration = editToadyAction.getDuration() / 10;
466 if (duration == 0)
467 Toady2.sequenceOfEdits.wait(0, 1);
468 else {
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);
481 else {
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)
508 return;
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);
530 else
531 SwingUtilities.invokeLater(new Runnable() {
532 public void run() {
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();