1   package debugger.gui.debugging;
2   
3   import javax.swing.text.*;
4   import java.util.Hashtable;
5   import java.awt.*;
6   
7   /**
8    * Copyright (c) Ontos AG (http://www.ontosearch.com).
9    * This class is part of JAPE Debugger component for
10   * GATE (Copyright (c) "The University of Sheffield" see http://gate.ac.uk/) <br>
11   * @author Oleg Mishenko
12   */
13  
14  class SyntaxDocument extends DefaultStyledDocument {
15      private DefaultStyledDocument doc;
16      private Element rootElement;
17  
18      private boolean multiLineComment;
19      private MutableAttributeSet normal;
20      private MutableAttributeSet keyword;
21      private MutableAttributeSet comment;
22      private MutableAttributeSet quote;
23  
24      private Hashtable keywords;
25  
26      public SyntaxDocument() {
27          doc = this;
28          rootElement = doc.getDefaultRootElement();
29          putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");
30  
31          normal = new SimpleAttributeSet();
32          StyleConstants.setForeground(normal, Color.black);
33  
34          comment = new SimpleAttributeSet();
35          StyleConstants.setForeground(comment, Color.gray);
36          StyleConstants.setItalic(comment, true);
37  
38          keyword = new SimpleAttributeSet();
39          StyleConstants.setForeground(keyword, Color.blue);
40  
41          quote = new SimpleAttributeSet();
42          StyleConstants.setForeground(quote, Color.red);
43  
44          Object dummyObject = new Object();
45          keywords = new Hashtable();
46          keywords.put("abstract", dummyObject);
47          keywords.put("boolean", dummyObject);
48          keywords.put("break", dummyObject);
49          keywords.put("byte", dummyObject);
50          keywords.put("byvalue", dummyObject);
51          keywords.put("case", dummyObject);
52          keywords.put("cast", dummyObject);
53          keywords.put("catch", dummyObject);
54          keywords.put("char", dummyObject);
55          keywords.put("class", dummyObject);
56          keywords.put("const", dummyObject);
57          keywords.put("continue", dummyObject);
58          keywords.put("default", dummyObject);
59          keywords.put("do", dummyObject);
60          keywords.put("double", dummyObject);
61          keywords.put("else", dummyObject);
62          keywords.put("extends", dummyObject);
63          keywords.put("false", dummyObject);
64          keywords.put("final", dummyObject);
65          keywords.put("finally", dummyObject);
66          keywords.put("float", dummyObject);
67          keywords.put("for", dummyObject);
68          keywords.put("future", dummyObject);
69          keywords.put("generic", dummyObject);
70          keywords.put("goto", dummyObject);
71          keywords.put("if", dummyObject);
72          keywords.put("implements", dummyObject);
73          keywords.put("import", dummyObject);
74          keywords.put("inner", dummyObject);
75          keywords.put("instanceof", dummyObject);
76          keywords.put("int", dummyObject);
77          keywords.put("interface", dummyObject);
78          keywords.put("long", dummyObject);
79          keywords.put("native", dummyObject);
80          keywords.put("new", dummyObject);
81          keywords.put("null", dummyObject);
82          keywords.put("operator", dummyObject);
83          keywords.put("outer", dummyObject);
84          keywords.put("package", dummyObject);
85          keywords.put("private", dummyObject);
86          keywords.put("protected", dummyObject);
87          keywords.put("public", dummyObject);
88          keywords.put("rest", dummyObject);
89          keywords.put("return", dummyObject);
90          keywords.put("short", dummyObject);
91          keywords.put("static", dummyObject);
92          keywords.put("super", dummyObject);
93          keywords.put("switch", dummyObject);
94          keywords.put("synchronized", dummyObject);
95          keywords.put("this", dummyObject);
96          keywords.put("throw", dummyObject);
97          keywords.put("throws", dummyObject);
98          keywords.put("transient", dummyObject);
99          keywords.put("true", dummyObject);
100         keywords.put("try", dummyObject);
101         keywords.put("var", dummyObject);
102         keywords.put("void", dummyObject);
103         keywords.put("volatile", dummyObject);
104         keywords.put("while", dummyObject);
105         keywords.put("Rule", dummyObject);
106         keywords.put("Phase", dummyObject);
107         keywords.put("Input", dummyObject);
108         keywords.put("Priority", dummyObject);
109         keywords.put("MultiPhase", dummyObject);
110         keywords.put("Macro", dummyObject);
111     }
112 
113     /*
114      *  Override to apply syntax highlighting after the document has been updated
115      */
116     public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
117         if (str.equals("{"))
118             str = addMatchingBrace(offset);
119 
120         super.insertString(offset, str, a);
121         processChangedLines(offset, str.length());
122     }
123 
124     /*
125      *  Override to apply syntax highlighting after the document has been updated
126      */
127     public void remove(int offset, int length) throws BadLocationException {
128         super.remove(offset, length);
129         processChangedLines(offset, 0);
130     }
131 
132     /*
133      *  Determine how many lines have been changed,
134      *  then apply highlighting to each line
135      */
136     private void processChangedLines(int offset, int length)
137             throws BadLocationException {
138         String content = doc.getText(0, doc.getLength());
139 
140         //  The lines affected by the latest document update
141 
142         int startLine = rootElement.getElementIndex(offset);
143         int endLine = rootElement.getElementIndex(offset + length);
144 
145         //  Make sure all comment lines prior to the start line are commented
146         //  and determine if the start line is still in a multi line comment
147 
148         setMultiLineComment(commentLinesBefore(content, startLine));
149 
150         //  Do the actual highlighting
151 
152         for (int i = startLine; i <= endLine; i++) {
153             applyHighlighting(content, i);
154         }
155 
156         //  Resolve highlighting to the next end multi line delimiter
157 
158         if (isMultiLineComment())
159             commentLinesAfter(content, endLine);
160         else
161             highlightLinesAfter(content, endLine);
162     }
163 
164     /*
165      *  Highlight lines when a multi line comment is still 'open'
166      *  (ie. matching end delimiter has not yet been encountered)
167      */
168     private boolean commentLinesBefore(String content, int line) {
169         int offset = rootElement.getElement(line).getStartOffset();
170 
171         //  Start of comment not found, nothing to do
172 
173         int startDelimiter = lastIndexOf(content, getStartDelimiter(), offset - 2);
174 
175         if (startDelimiter < 0)
176             return false;
177 
178         //  Matching start/end of comment found, nothing to do
179 
180         int endDelimiter = indexOf(content, getEndDelimiter(), startDelimiter);
181 
182         if (endDelimiter < offset & endDelimiter != -1)
183             return false;
184 
185         //  End of comment not found, highlight the lines
186 
187         doc.setCharacterAttributes(startDelimiter, offset - startDelimiter + 1, comment, false);
188         return true;
189     }
190 
191     /*
192      *  Highlight comment lines to matching end delimiter
193      */
194     private void commentLinesAfter(String content, int line) {
195         int offset = rootElement.getElement(line).getEndOffset();
196 
197         //  End of comment not found, nothing to do
198 
199         int endDelimiter = indexOf(content, getEndDelimiter(), offset);
200 
201         if (endDelimiter < 0)
202             return;
203 
204         //  Matching start/end of comment found, comment the lines
205 
206         int startDelimiter = lastIndexOf(content, getStartDelimiter(), endDelimiter);
207 
208         if (startDelimiter < 0 || startDelimiter <= offset) {
209             doc.setCharacterAttributes(offset, endDelimiter - offset + 1, comment, false);
210         }
211     }
212 
213     /*
214      *  Highlight lines to start or end delimiter
215      */
216     private void highlightLinesAfter(String content, int line) {
217         int offset = rootElement.getElement(line).getEndOffset();
218 
219         //  Start/End delimiter not found, nothing to do
220 
221         int startDelimiter = indexOf(content, getStartDelimiter(), offset);
222         int endDelimiter = indexOf(content, getEndDelimiter(), offset);
223 
224         if (startDelimiter < 0)
225             startDelimiter = content.length();
226 
227         if (endDelimiter < 0)
228             endDelimiter = content.length();
229 
230         int delimiter = Math.min(startDelimiter, endDelimiter);
231 
232         if (delimiter < offset)
233             return;
234 
235         //  Start/End delimiter found, reapply highlighting
236 
237         int endLine = rootElement.getElementIndex(delimiter);
238 
239         for (int i = line + 1; i < endLine; i++) {
240             Element branch = rootElement.getElement(i);
241             Element leaf = doc.getCharacterElement(branch.getStartOffset());
242             AttributeSet as = leaf.getAttributes();
243 
244             if (as.isEqual(comment))
245                 applyHighlighting(content, i);
246         }
247     }
248 
249     /*
250      *  Parse the line to determine the appropriate highlighting
251      */
252     private void applyHighlighting(String content, int line) {
253         int startOffset = rootElement.getElement(line).getStartOffset();
254         int endOffset = rootElement.getElement(line).getEndOffset() - 1;
255 
256         int lineLength = endOffset - startOffset;
257         int contentLength = content.length();
258 
259         if (endOffset >= contentLength)
260             endOffset = contentLength - 1;
261 
262         //  check for multi line comments
263         //  (always set the comment attribute for the entire line)
264 
265         if (endingMultiLineComment(content, startOffset, endOffset)
266                 || isMultiLineComment()
267                 || startingMultiLineComment(content, startOffset, endOffset)) {
268             doc.setCharacterAttributes(startOffset, endOffset - startOffset + 1, comment, false);
269             return;
270         }
271 
272         //  set normal attributes for the line
273 
274         doc.setCharacterAttributes(startOffset, lineLength, normal, true);
275 
276         //  check for single line comment
277 
278         int index = content.indexOf(getSingleLineDelimiter(), startOffset);
279 
280         if ((index > -1) && (index < endOffset)) {
281             doc.setCharacterAttributes(index, endOffset - index + 1, comment, false);
282             endOffset = index - 1;
283         }
284 
285         //  check for tokens
286 
287         checkForTokens(content, startOffset, endOffset);
288     }
289 
290 /*
291     public String getText(int offset, int length) throws BadLocationException
292     {
293         return DebugController.getInstance().getRuleController().getJapeText().substring(offset, offset + length);
294     }
295 */
296 
297     /*
298      *  Does this line contain the start delimiter
299      */
300     private boolean startingMultiLineComment(String content, int startOffset, int endOffset) {
301         int index = indexOf(content, getStartDelimiter(), startOffset);
302 
303         if ((index < 0) || (index > endOffset))
304             return false;
305         else {
306             setMultiLineComment(true);
307             return true;
308         }
309     }
310 
311     /*
312      *  Does this line contain the end delimiter
313      */
314     private boolean endingMultiLineComment(String content, int startOffset, int endOffset) {
315         int index = indexOf(content, getEndDelimiter(), startOffset);
316 
317         if ((index < 0) || (index > endOffset))
318             return false;
319         else {
320             setMultiLineComment(false);
321             return true;
322         }
323     }
324 
325     /*
326      *  We have found a start delimiter
327      *  and are still searching for the end delimiter
328      */
329     private boolean isMultiLineComment() {
330         return multiLineComment;
331     }
332 
333     private void setMultiLineComment(boolean value) {
334         multiLineComment = value;
335     }
336 
337     /*
338      *  Parse the line for tokens to highlight
339      */
340     private void checkForTokens(String content, int startOffset, int endOffset) {
341         while (startOffset <= endOffset) {
342             //  skip the delimiters to find the start of a new token
343 
344             while (isDelimiter(content.substring(startOffset, startOffset + 1))) {
345                 if (startOffset < endOffset)
346                     startOffset++;
347                 else
348                     return;
349             }
350 
351             //  Extract and process the entire token
352 
353             if (isQuoteDelimiter(content.substring(startOffset, startOffset + 1)))
354                 startOffset = getQuoteToken(content, startOffset, endOffset);
355             else
356                 startOffset = getOtherToken(content, startOffset, endOffset);
357         }
358     }
359 
360     /*
361      *
362      */
363     private int getQuoteToken(String content, int startOffset, int endOffset) {
364         String quoteDelimiter = content.substring(startOffset, startOffset + 1);
365         String escapeString = getEscapeString(quoteDelimiter);
366 
367         int index;
368         int endOfQuote = startOffset;
369 
370         //  skip over the escape quotes in this quote
371 
372         index = content.indexOf(escapeString, endOfQuote + 1);
373 
374         while ((index > -1) && (index < endOffset)) {
375             endOfQuote = index + 1;
376             index = content.indexOf(escapeString, endOfQuote);
377         }
378 
379         // now find the matching delimiter
380 
381         index = content.indexOf(quoteDelimiter, endOfQuote + 1);
382 
383         if ((index < 0) || (index > endOffset))
384             endOfQuote = endOffset;
385         else
386             endOfQuote = index;
387 
388         doc.setCharacterAttributes(startOffset, endOfQuote - startOffset + 1, quote, false);
389 
390         return endOfQuote + 1;
391     }
392 
393     /*
394      *
395      */
396     private int getOtherToken(String content, int startOffset, int endOffset) {
397         int endOfToken = startOffset + 1;
398 
399         while (endOfToken <= endOffset) {
400             if (isDelimiter(content.substring(endOfToken, endOfToken + 1)))
401                 break;
402 
403             endOfToken++;
404         }
405 
406         String token = content.substring(startOffset, endOfToken);
407 
408         if (isKeyword(token))
409             doc.setCharacterAttributes(startOffset, endOfToken - startOffset, keyword, false);
410 
411         return endOfToken + 1;
412     }
413 
414     /*
415      *  Assume the needle will the found at the start/end of the line
416      */
417     private int indexOf(String content, String needle, int offset) {
418         int index;
419 
420         while ((index = content.indexOf(needle, offset)) != -1) {
421             String text = getLine(content, index).trim();
422 
423             if (text.startsWith(needle) || text.endsWith(needle))
424                 break;
425             else
426                 offset = index + 1;
427         }
428 
429         return index;
430     }
431 
432     /*
433      *  Assume the needle will the found at the start/end of the line
434      */
435     private int lastIndexOf(String content, String needle, int offset) {
436         int index;
437 
438         while ((index = content.lastIndexOf(needle, offset)) != -1) {
439             String text = getLine(content, index).trim();
440 
441             if (text.startsWith(needle) || text.endsWith(needle))
442                 break;
443             else
444                 offset = index - 1;
445         }
446 
447         return index;
448     }
449 
450     private String getLine(String content, int offset) {
451         int line = rootElement.getElementIndex(offset);
452         Element lineElement = rootElement.getElement(line);
453         int start = lineElement.getStartOffset();
454         int end = lineElement.getEndOffset();
455         return content.substring(start, end - 1);
456     }
457 
458     /*
459      *  Override for other languages
460      */
461     protected boolean isDelimiter(String character) {
462         String operands = ";:{}()[]+-/%<=>!&|^~*";
463 
464         if (Character.isWhitespace(character.charAt(0)) ||
465                 operands.indexOf(character) != -1)
466             return true;
467         else
468             return false;
469     }
470 
471     /*
472      *  Override for other languages
473      */
474     protected boolean isQuoteDelimiter(String character) {
475         String quoteDelimiters = "\"'";
476 
477         if (quoteDelimiters.indexOf(character) < 0)
478             return false;
479         else
480             return true;
481     }
482 
483     /*
484      *  Override for other languages
485      */
486     protected boolean isKeyword(String token) {
487         Object o = keywords.get(token);
488 
489         return o == null ? false : true;
490     }
491 
492     /*
493      *  Override for other languages
494      */
495     protected String getStartDelimiter() {
496         return "/*";
497     }
498 
499     /*
500      *  Override for other languages
501      */
502     protected String getEndDelimiter() {
503         return "*/";
504     }
505 
506     /*
507      *  Override for other languages
508      */
509     protected String getSingleLineDelimiter() {
510         return "//";
511     }
512 
513     /*
514      *  Override for other languages
515      */
516     protected String getEscapeString(String quoteDelimiter) {
517         return "\\" + quoteDelimiter;
518     }
519 
520     /*
521      *
522      */
523     protected String addMatchingBrace(int offset) throws BadLocationException {
524         StringBuffer whiteSpace = new StringBuffer();
525         int line = rootElement.getElementIndex(offset);
526         int i = rootElement.getElement(line).getStartOffset();
527 
528         while (true) {
529             String temp = doc.getText(i, 1);
530 
531             if (temp.equals(" ") || temp.equals("\t")) {
532                 whiteSpace.append(temp);
533                 i++;
534             } else
535                 break;
536         }
537 
538         return "{\n" + whiteSpace.toString() + "\t\n" + whiteSpace.toString() + "}";
539     }
540 }
541 
542 
543