1 package org.devaki.nextobjects.ui.editor;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.FileReader;
24 import java.io.IOException;
25 import java.io.Reader;
26 import java.util.Comparator;
27 import java.util.HashSet;
28 import java.util.Hashtable;
29 import java.util.Iterator;
30 import java.util.NoSuchElementException;
31 import java.util.SortedSet;
32 import java.util.TreeSet;
33 import java.util.Vector;
34 import java.awt.Color;
35 import java.awt.GridBagConstraints;
36 import java.awt.GridBagLayout;
37 import java.awt.Insets;
38 import java.awt.event.MouseEvent;
39 import javax.swing.JPanel;
40 import javax.swing.JScrollPane;
41 import javax.swing.JTextPane;
42 import javax.swing.text.AbstractDocument;
43 import javax.swing.text.AttributeSet;
44 import javax.swing.text.BadLocationException;
45 import javax.swing.text.DefaultStyledDocument;
46 import javax.swing.text.SimpleAttributeSet;
47 import javax.swing.text.StyleConstants;
48 import javax.swing.event.MouseInputAdapter;
49 import org.devaki.nextobjects.ui.editor.syntax.Lexer;
50 import org.devaki.nextobjects.ui.editor.syntax.SqlLexer;
51 import org.devaki.nextobjects.ui.editor.syntax.Token;
52 import org.devaki.nextobjects.ui.menus.MenuFactory;
53 public class SqlEditor extends JPanel
54 {
55 /***
56 * The place where to draw text
57 */
58 private static JTextPane jTextPane;
59 /***
60 * the styled document that is the model for the textPane
61 **/
62 private static HighLightedDocument document;
63 /***
64 * A reader wrapped around the document so that the document can be fed into
65 */
66 private static DocumentReader documentReader;
67 /***
68 *The lexer that tells us what colors different words should be
69 */
70 private static Lexer syntaxLexer;
71 /***
72 * A thread that handles the actual coloring
73 */
74 private static Colorer colorer;
75 /***
76 * A lock for modifying the document, or for actions that depend on the
77 * document not being modified.
78 */
79 private Object fDoclock = new Object();
80 /***
81 * A hashtable containing the text styles.
82 */
83 private static Hashtable styles = new Hashtable();
84 /***
85 * This class contain the textpane of the SQL editor
86 *
87 */
88 public SqlEditor()
89 {
90
91 super(true);
92
93
94 document = new HighLightedDocument();
95 SqlEditor.jTextPane = new JTextPane(document);
96
97 jTextPane.addMouseListener(new MouseInputAdapter()
98 {
99 public void mousePressed(MouseEvent evt)
100 {
101 if (evt.isPopupTrigger())
102 {
103 MenuFactory.getEditorPopupMenu().setTextpane(jTextPane);
104 MenuFactory.getEditorPopupMenu().show(evt.getComponent(), evt.getX(), evt.getY());
105 }
106 }
107 });
108 colorer = new Colorer();
109 colorer.start();
110 this.initStyles();
111 documentReader = new DocumentReader(document);
112 syntaxLexer = new SqlLexer(documentReader);
113
114 this.setLayout(new GridBagLayout());
115
116
117 this.add(
118 new JScrollPane(jTextPane),
119 new GridBagConstraints(
120 0,
121 0,
122 1,
123 1,
124 1.0,
125 1.0,
126 GridBagConstraints.CENTER,
127 GridBagConstraints.BOTH,
128 new Insets(3, 3, 3, 3),
129 0,
130 0));
131 }
132 /***
133 * Edit the file given as paramater
134 * @param pFile
135 */
136 public static void setFile(File pFile)
137 {
138 try
139 {
140 document.remove(0, document.getLength());
141 BufferedReader reader = new BufferedReader(new FileReader(pFile));
142 String tmp;
143 while ((tmp = reader.readLine()) != null)
144 {
145 document.insertString(document.getLength(), tmp + "\n", null);
146 }
147 reader.close();
148 }
149 catch (IOException ex)
150 {
151 }
152 catch (BadLocationException ex)
153 {
154 }
155 }
156 /***
157 * Description: Run the Syntax Highlighting as a separate thread.
158 * Things that need to be colored are messaged to the
159 * thread and put in a list.
160 */
161 private class Colorer extends Thread
162 {
163 /*** Initial places detected when the lexer has been initialized */
164 private TreeSet fIniPositions =
165 new TreeSet(new DocPositionComparator());
166 /*** New places detected */
167 private HashSet newPositions = new HashSet();
168 /*** Vector that stores the communication between the two threads */
169 private volatile Vector v = new Vector();
170 /*** Changes that occured before the currently highlighting place */
171 private volatile int change = 0;
172 /*** The last position colored */
173 private volatile int lastPosition = -1;
174 /*** ?? */
175 private volatile boolean asleep = false;
176 /*** Synchronize 'this' object */
177 private Object lock = new Object();
178 /*** A simple wrapper representing something that needs to be colored */
179 private class RecolorEvent
180 {
181 /*** position */
182 public int position;
183 /*** adjustement */
184 public int adjustment;
185 public RecolorEvent(int pPosition, int pAdjustment)
186 {
187 this.position = pPosition;
188 this.adjustment = pAdjustment;
189 }
190 }
191 /***
192 * Tell the Syntax Highlighting thread to take another look at this section
193 * of the document. It will process this as a FIFO.
194 * This method should be done inside a doclock.
195 * @param position position
196 * @param adjustment adjustement
197 */
198 public void color(int position, int adjustment)
199 {
200 if (position < lastPosition)
201 {
202 if (lastPosition < position - adjustment)
203 {
204 change -= lastPosition - position;
205 }
206 else
207 {
208 change += adjustment;
209 }
210 }
211 synchronized (lock)
212 {
213 v.add(new RecolorEvent(position, adjustment));
214 if (asleep)
215 {
216 this.interrupt();
217 }
218 }
219 }
220 /***
221 * The colorer runs forever and may sleep for long periods of time.
222 * It should be interrupted every time there is something for it to do.
223 */
224 public void run()
225 {
226 int position = -1;
227 int adjustment = 0;
228
229
230 boolean tryAgain = false;
231 for (;;)
232 {
233 synchronized (lock)
234 {
235 if (v.size() > 0)
236 {
237 RecolorEvent re = (RecolorEvent) (v.elementAt(0));
238 v.removeElementAt(0);
239 position = re.position;
240 adjustment = re.adjustment;
241 }
242 else
243 {
244 tryAgain = false;
245 position = -1;
246 adjustment = 0;
247 }
248 }
249 if (position != -1)
250 {
251 SortedSet workingSet;
252 Iterator workingIt;
253 DocPosition startRequest = new DocPosition(position);
254 DocPosition endRequest =
255 new DocPosition(
256 position
257 + ((adjustment >= 0) ? adjustment : -adjustment));
258 DocPosition dp;
259 DocPosition dpStart = null;
260 DocPosition dpEnd = null;
261
262 try
263 {
264
265 workingSet = fIniPositions.headSet(startRequest);
266
267 dpStart = ((DocPosition) workingSet.last());
268 }
269 catch (NoSuchElementException x)
270 {
271
272
273 dpStart = new DocPosition(0);
274 }
275
276 if (adjustment < 0)
277 {
278 workingSet =
279 fIniPositions.subSet(startRequest, endRequest);
280 workingIt = workingSet.iterator();
281 while (workingIt.hasNext())
282 {
283 workingIt.next();
284 workingIt.remove();
285 }
286 }
287
288 workingSet = fIniPositions.tailSet(startRequest);
289 workingIt = workingSet.iterator();
290 while (workingIt.hasNext())
291 {
292 ((DocPosition) workingIt.next()).adjustPosition(
293 adjustment);
294 }
295
296 workingSet = fIniPositions.tailSet(dpStart);
297 workingIt = workingSet.iterator();
298 dp = null;
299 if (workingIt.hasNext())
300 {
301 dp = (DocPosition) workingIt.next();
302 }
303 try
304 {
305 Token t;
306 boolean done = false;
307 dpEnd = dpStart;
308 synchronized (fDoclock)
309 {
310
311
312
313
314
315
316
317 syntaxLexer.reset(
318 documentReader,
319 0,
320 dpStart.getPosition(),
321 0);
322
323
324 documentReader.seek(dpStart.getPosition());
325
326
327
328
329 t = syntaxLexer.getNextToken();
330 }
331 newPositions.add(dpStart);
332 while (!done && t != null)
333 {
334
335
336
337 synchronized (fDoclock)
338 {
339 if (t.getCharEnd() <= document.getLength())
340 {
341 document.setCharacterAttributes(
342 t.getCharBegin() + change,
343 t.getCharEnd() - t.getCharBegin(),
344 getStyle(t.getDescription()),
345 true);
346
347 dpEnd = new DocPosition(t.getCharEnd());
348 }
349 lastPosition = (t.getCharEnd() + change);
350 }
351
352
353
354
355
356
357
358 if (t.getState() == Token.INITIAL_STATE)
359 {
360
361
362
363 while (dp != null
364 && dp.getPosition() <= t.getCharEnd())
365 {
366 if (dp.getPosition() == t.getCharEnd()
367 && dp.getPosition()
368 >= endRequest.getPosition())
369 {
370
371 done = true;
372 dp = null;
373 }
374 else if (workingIt.hasNext())
375 {
376
377 dp = (DocPosition) workingIt.next();
378 }
379 else
380 {
381
382
383
384 dp = null;
385 }
386 }
387
388
389 newPositions.add(dpEnd);
390 }
391 synchronized (fDoclock)
392 {
393 t = syntaxLexer.getNextToken();
394 }
395 }
396
397
398
399 workingIt =
400 fIniPositions.subSet(dpStart, dpEnd).iterator();
401 while (workingIt.hasNext())
402 {
403 workingIt.next();
404 workingIt.remove();
405 }
406
407 workingIt =
408 fIniPositions
409 .tailSet(new DocPosition(document.getLength()))
410 .iterator();
411 while (workingIt.hasNext())
412 {
413 workingIt.next();
414 workingIt.remove();
415 }
416
417 fIniPositions.addAll(newPositions);
418 newPositions.clear();
419 }
420 catch (IOException x)
421 {
422 }
423 synchronized (fDoclock)
424 {
425 lastPosition = -1;
426 change = 0;
427 }
428
429
430 tryAgain = true;
431 }
432 asleep = true;
433 if (!tryAgain)
434 {
435 try
436 {
437 sleep(0xffffff);
438 }
439 catch (InterruptedException x)
440 {
441 }
442 }
443 asleep = false;
444 }
445 }
446 }
447 /***
448 * Color a section of the document.
449 * The actual coloring will start somewhere before the requested position
450 * and continue as long as needed.
451 * @param position the starting point for the coloring.
452 * @param adjustment amount of text inserted or removed at the starting point.
453 */
454 public static void color(int position, int adjustment)
455 {
456 colorer.color(position, adjustment);
457 }
458 /***
459 * retrieve the syle for the given type of text.
460 * @param styleName the label for the type of text ("tag" for example)
461 * or null if the styleName is not known.
462 * @return the style
463 */
464 private static SimpleAttributeSet getStyle(String styleName)
465 {
466 return ((SimpleAttributeSet) styles.get(styleName));
467 }
468 /***
469 * Create the styles and place them in the hash table.
470 */
471 private void initStyles()
472 {
473 SimpleAttributeSet style;
474 style = new SimpleAttributeSet();
475 StyleConstants.setFontFamily(style, "Monospaced");
476 StyleConstants.setFontSize(style, 11);
477 StyleConstants.setBackground(style, Color.white);
478 StyleConstants.setForeground(style, Color.blue);
479 StyleConstants.setBold(style, false);
480 StyleConstants.setItalic(style, false);
481 styles.put("reservedWord", style);
482 style = new SimpleAttributeSet();
483 StyleConstants.setFontFamily(style, "Monospaced");
484 StyleConstants.setFontSize(style, 11);
485 StyleConstants.setBackground(style, Color.white);
486 StyleConstants.setForeground(style, Color.black);
487 StyleConstants.setBold(style, false);
488 StyleConstants.setItalic(style, false);
489 styles.put("identifier", style);
490 style = new SimpleAttributeSet();
491 StyleConstants.setFontFamily(style, "Monospaced");
492 StyleConstants.setFontSize(style, 11);
493 StyleConstants.setBackground(style, Color.white);
494 StyleConstants.setForeground(style, new Color(0xB03060)
495 );
496 StyleConstants.setBold(style, false);
497 StyleConstants.setItalic(style, false);
498 styles.put("literal", style);
499 style = new SimpleAttributeSet();
500 StyleConstants.setFontFamily(style, "Monospaced");
501 StyleConstants.setFontSize(style, 11);
502 StyleConstants.setBackground(style, Color.white);
503 StyleConstants.setForeground(style, new Color(0x000080)
504 );
505 StyleConstants.setBold(style, false);
506 StyleConstants.setItalic(style, false);
507 styles.put("separator", style);
508 style = new SimpleAttributeSet();
509 StyleConstants.setFontFamily(style, "Monospaced");
510 StyleConstants.setFontSize(style, 11);
511 StyleConstants.setBackground(style, Color.white);
512 StyleConstants.setForeground(style, Color.black);
513 StyleConstants.setBold(style, true);
514 StyleConstants.setItalic(style, false);
515 styles.put("operator", style);
516 style = new SimpleAttributeSet();
517 StyleConstants.setFontFamily(style, "Monospaced");
518 StyleConstants.setFontSize(style, 11);
519 StyleConstants.setBackground(style, Color.white);
520 StyleConstants.setForeground(style, Color.green.darker());
521 StyleConstants.setBold(style, false);
522 StyleConstants.setItalic(style, false);
523 styles.put("comment", style);
524 style = new SimpleAttributeSet();
525 StyleConstants.setFontFamily(style, "Monospaced");
526 StyleConstants.setFontSize(style, 11);
527 StyleConstants.setBackground(style, Color.white);
528 StyleConstants.setForeground(style, Color.black);
529 StyleConstants.setBold(style, false);
530 StyleConstants.setItalic(style, false);
531 styles.put("whitespace", style);
532 style = new SimpleAttributeSet();
533 StyleConstants.setFontFamily(style, "Monospaced");
534 StyleConstants.setFontSize(style, 12);
535 StyleConstants.setBackground(style, Color.white);
536 StyleConstants.setForeground(style, Color.red);
537 StyleConstants.setBold(style, false);
538 StyleConstants.setItalic(style, false);
539 styles.put("error", style);
540 style = new SimpleAttributeSet();
541 StyleConstants.setFontFamily(style, "Monospaced");
542 StyleConstants.setFontSize(style, 11);
543 StyleConstants.setBackground(style, Color.white);
544 StyleConstants.setForeground(style, Color.orange);
545 StyleConstants.setBold(style, false);
546 StyleConstants.setItalic(style, false);
547 styles.put("unknown", style);
548 }
549 /***
550 * Just like a DefaultStyledDocument but intercepts inserts and removes
551 * to color them.
552 */
553 private class HighLightedDocument extends DefaultStyledDocument
554 {
555 public void insertString(int offs, String str, AttributeSet a)
556 throws BadLocationException
557 {
558 synchronized (fDoclock)
559 {
560 super.insertString(offs, str, a);
561 color(offs, str.length());
562 documentReader.update(offs, str.length());
563 }
564 }
565 public void remove(int offs, int len) throws BadLocationException
566 {
567 synchronized (fDoclock)
568 {
569 super.remove(offs, len);
570 color(offs, -len);
571 documentReader.update(offs, -len);
572 }
573 }
574 }
575 }
576 /***
577 * A wrapper for a position in a document appropriate for storing
578 * in a collection.
579 */
580 class DocPosition
581 {
582
583 private int position;
584 /***
585 * Get the position represented by this DocPosition
586 * @return the position
587 */
588 int getPosition()
589 {
590 return position;
591 }
592 /***
593 * Construct a DocPosition from the given offset into the document.
594 * @param position The position this DocObject will represent
595 */
596 public DocPosition(int pPosition)
597 {
598 this.position = pPosition;
599 }
600 /***
601 * Adjust this position.
602 * @param adjustment amount to adjust this position.
603 * @return the DocPosition, adjusted properly.
604 */
605 public DocPosition adjustPosition(int adjustment)
606 {
607 position += adjustment;
608 return this;
609 }
610 /***
611 * Two DocPositions are equal iff they have the same internal position.
612 * @param obj the object
613 * @return if this DocPosition represents the same position as another.
614 */
615 public final boolean equals(final Object obj)
616 {
617 if (obj instanceof DocPosition)
618 {
619 DocPosition d = (DocPosition) (obj);
620 return this.position == d.position;
621 }
622 else
623 {
624 return false;
625 }
626 }
627 /***
628 * A string representation useful for debugging.
629 * @return A string representing the position.
630 */
631 public String toString()
632 {
633 return "" + position;
634 }
635 }
636 /***
637 * A comparator appropriate for use with Collections of
638 * DocPositions.
639 */
640 class DocPositionComparator implements Comparator
641 {
642 /***
643 * Comparator
644 * @param object to compare
645 * @return true for DocPositionComparators, false otherwise.
646 */
647 public boolean equals(Object obj)
648 {
649 return obj instanceof DocPositionComparator;
650 }
651 /***
652 * Compare two DocPositions
653 * @param o1 first DocPosition
654 * @param o2 second DocPosition
655 * @return negative,0 or positive
656 */
657 public int compare(final Object o1, final Object o2)
658 {
659 if (o1 instanceof DocPosition && o2 instanceof DocPosition)
660 {
661 DocPosition d1 = (DocPosition) (o1);
662 DocPosition d2 = (DocPosition) (o2);
663 return (d1.getPosition() - d2.getPosition());
664 }
665 else if (o1 instanceof DocPosition)
666 {
667 return -1;
668 }
669 else if (o2 instanceof DocPosition)
670 {
671 return 1;
672 }
673 else if (o1.hashCode() < o2.hashCode())
674 {
675 return -1;
676 }
677 else if (o2.hashCode() > o1.hashCode())
678 {
679 return 1;
680 }
681 else
682 {
683 return 0;
684 }
685 }
686 }
687 /***
688 * A reader interface for an abstract document. Since the syntax highlighting
689 * packages only accept Stings and Readers, this must be used.
690 * Since the close() method does nothing and a seek() method has been added,
691 * this allows us to get some performance improvements through reuse.
692 * It can be used even after the lexer explicitly closes it by seeking to the
693 * place that we want to read next, and reseting the lexer.
694 */
695 class DocumentReader extends Reader
696 {
697 /*** Current position in the document */
698 private long position = 0;
699 /*** Saved position used in the mark and reset method */
700 private long mark = -1;
701 /*** The document that we are working with */
702 private AbstractDocument document;
703 /***
704 * Alerting the reader
705 * @param pPosition position
706 * @param adjustment adjustement
707 */
708 public void update(int pPosition, int adjustment)
709 {
710 if (pPosition < this.position)
711 {
712 if (this.position < pPosition - adjustment)
713 {
714 this.position = pPosition;
715 }
716 else
717 {
718 this.position += adjustment;
719 }
720 }
721 }
722 /***
723 * Construct a reader on the given document.
724 * @param document the document to be read.
725 */
726 public DocumentReader(AbstractDocument pDocument)
727 {
728 this.document = pDocument;
729 }
730 /***
731 * Has no effect. This reader can be used even after it has been closed.
732 */
733 public void close()
734 {
735 }
736 /***
737 * Save a position for reset.
738 * @param readAheadLimit ignored.
739 */
740 public void mark(int readAheadLimit)
741 {
742 mark = position;
743 }
744 /***
745 * This reader support mark and reset.
746 * @return true
747 */
748 public boolean markSupported()
749 {
750 return true;
751 }
752 /***
753 * Read a single character.
754 * @return the character or -1 if the end of the document has been reached.
755 */
756 public int read()
757 {
758 if (position < document.getLength())
759 {
760 try
761 {
762 char c = document.getText((int) position, 1).charAt(0);
763 position++;
764 return c;
765 }
766 catch (BadLocationException x)
767 {
768 return -1;
769 }
770 }
771 else
772 {
773 return -1;
774 }
775 }
776 /***
777 * Read and fill the buffer.
778 * @param cbuf the buffer to fill.
779 * @return the number of characters read
780 */
781 public int read(char[] cbuf)
782 {
783 return read(cbuf, 0, cbuf.length);
784 }
785 /***
786 * Read and fill the buffer.
787 * @param cbuf the buffer to fill.
788 * @param off offset into the buffer to begin the fill.
789 * @param len maximum number of characters to put in the buffer.
790 * @return the number of characters read
791 */
792 public int read(char[] cbuf, int off, int len)
793 {
794 if (position < document.getLength())
795 {
796 int length = len;
797 if (position + length >= document.getLength())
798 {
799 length = document.getLength() - (int) position;
800 }
801 if (off + length >= cbuf.length)
802 {
803 length = cbuf.length - off;
804 }
805 try
806 {
807 String s = document.getText((int) position, length);
808 position += length;
809 for (int i = 0; i < length; i++)
810 {
811 cbuf[off + i] = s.charAt(i);
812 }
813 return length;
814 }
815 catch (BadLocationException x)
816 {
817 return -1;
818 }
819 }
820 else
821 {
822 return -1;
823 }
824 }
825 /***
826 * @return true
827 */
828 public boolean ready()
829 {
830 return true;
831 }
832 /***
833 * Reset this reader to the last mark, or the beginning of the document
834 * if a mark has not been set.
835 */
836 public void reset()
837 {
838 if (mark == -1)
839 {
840 position = 0;
841 }
842 else
843 {
844 position = mark;
845 }
846 mark = -1;
847 }
848 /***
849 * Skip characters of input.
850 * @param n number of characters to skip.
851 * @return the actual number of characters skipped.
852 */
853 public long skip(long n)
854 {
855 if (position + n <= document.getLength())
856 {
857 position += n;
858 return n;
859 }
860 else
861 {
862 long oldPos = position;
863 position = document.getLength();
864 return (document.getLength() - oldPos);
865 }
866 }
867 /***
868 * Seek to the given position in the document
869 * @param n the offset to which to seek.
870 */
871 public final void seek(final long n)
872 {
873 if (n <= document.getLength())
874 {
875 position = n;
876 }
877 else
878 {
879 position = document.getLength();
880 }
881 }
882 }