001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.sql.framework.ui.output;
042:
043: import java.awt.Color;
044: import java.util.ArrayList;
045: import java.util.Arrays;
046: import java.util.HashSet;
047:
048: import java.util.List;
049: import javax.swing.JEditorPane;
050: import javax.swing.text.AttributeSet;
051: import javax.swing.text.BadLocationException;
052: import javax.swing.text.DefaultEditorKit;
053: import javax.swing.text.DefaultStyledDocument;
054: import javax.swing.text.Element;
055: import javax.swing.text.MutableAttributeSet;
056: import javax.swing.text.SimpleAttributeSet;
057: import javax.swing.text.StyleConstants;
058:
059: /**
060: * @author Nithya Radhakrishnan
061: */
062: public class SyntaxDocument extends DefaultStyledDocument {
063:
064: private DefaultStyledDocument doc = null;
065: private Element rootElement = null;
066: private boolean multiLineComment = false;
067: private MutableAttributeSet normal = null;
068: private MutableAttributeSet keyword = null;
069: private MutableAttributeSet comment = null;
070: private MutableAttributeSet quote = null;
071: private HashSet keywords = null;
072: private SQLEditorPanel sqlPane;
073: private List dictionary = new ArrayList();
074: private JEditorPane comp;
075:
076: public SyntaxDocument(String[] syntax) {
077: this (syntax, Color.BLACK);
078: }
079:
080: public SyntaxDocument(JEditorPane field, String[] aDictionary) {
081: comp = field;
082: dictionary.addAll(Arrays.asList(aDictionary));
083: }
084:
085: public void addDictionaryEntry(String item) {
086: dictionary.add(item);
087: }
088:
089: public SyntaxDocument(SQLEditorPanel sqlPane) {
090: super ();
091: this .sqlPane = sqlPane;
092: }
093:
094: public SyntaxDocument(String[] syntax, Color normal) {
095: this (syntax, normal, Color.GRAY);
096: }
097:
098: public SyntaxDocument(String[] syntax, Color normal, Color comments) {
099: this (syntax, normal, comments, Color.BLUE);
100: }
101:
102: public SyntaxDocument(String[] syntax, Color normal,
103: Color comments, Color keyword) {
104: this (syntax, normal, comments, keyword, Color.RED);
105: }
106:
107: public SyntaxDocument(String[] syntax, Color normal,
108: Color comments, Color keyword, Color quote) {
109: this .init(syntax, normal, comments, keyword, quote);
110: }
111:
112: public void init(String[] syntax, Color normal, Color comment,
113: Color keyword, Color quote) {
114:
115: this .doc = this ;
116:
117: //End of line
118: this .rootElement = this .doc.getDefaultRootElement();
119: this
120: .putProperty(DefaultEditorKit.EndOfLineStringProperty,
121: "\n");
122:
123: //String Colouring
124: this .normal = new SimpleAttributeSet();
125: StyleConstants.setForeground(this .normal, normal);
126:
127: this .comment = new SimpleAttributeSet();
128: StyleConstants.setForeground(this .comment, comment);
129:
130: this .keyword = new SimpleAttributeSet();
131: StyleConstants.setForeground(this .keyword, keyword);
132:
133: this .quote = new SimpleAttributeSet();
134: StyleConstants.setForeground(this .quote, quote);
135:
136: this .keywords = new HashSet(syntax.length);
137: for (int i = 0; i < syntax.length; i++) {
138: this .keywords.add(syntax[i]);
139: }
140: }
141:
142: /**
143: * Override to apply syntax highlighting after the document has been updated
144: */
145: @Override
146: public void insertString(int offset, String str, AttributeSet a)
147: throws BadLocationException {
148: if (str.equals("(")) {
149: str = addMatchingBrace(offset);
150: }
151:
152: super .insertString(offset, str, a);
153: processChangedLines(offset, str.length());
154:
155: }
156:
157: /**
158: * Override to apply syntax highlighting after the document has been updated
159: */
160: @Override
161: public void remove(int offset, int length)
162: throws BadLocationException {
163: super .remove(offset, length);
164: processChangedLines(offset, 0);
165: }
166:
167: /**
168: * Determine how many lines have been changed,
169: * then apply highlighting to each line
170: */
171: public void processChangedLines(int offset, int length)
172: throws BadLocationException {
173: String content = doc.getText(0, doc.getLength());
174:
175: // The lines affected by the latest document update
176: int startLine = rootElement.getElementIndex(offset);
177: int endLine = rootElement.getElementIndex(offset + length);
178:
179: // Make sure all comment lines prior to the start line are commented
180: // and determine if the start line is still in a multi line comment
181: setMultiLineComment(commentLinesBefore(content, startLine));
182:
183: // Do the actual highlighting
184: for (int i = startLine; i <= endLine; i++) {
185: applyHighlighting(content, i);
186: }
187:
188: // Resolve highlighting to the next end multi line delimiter
189: if (isMultiLineComment()) {
190: commentLinesAfter(content, endLine);
191: } else {
192: highlightLinesAfter(content, endLine);
193: }
194: }
195:
196: /**
197: * Highlight lines when a multi line comment is still 'open'
198: * (ie. matching end delimiter has not yet been encountered)
199: */
200: private boolean commentLinesBefore(String content, int line) {
201: int offset = rootElement.getElement(line).getStartOffset();
202:
203: // Start of comment not found, nothing to do
204: int startDelimiter = lastIndexOf(content, getStartDelimiter(),
205: offset - 2);
206: if (startDelimiter < 0) {
207: return false;
208: }
209:
210: // Matching start/end of comment found, nothing to do
211: int endDelimiter = indexOf(content, getEndDelimiter(),
212: startDelimiter);
213:
214: if (endDelimiter < offset & endDelimiter != -1) {
215: return false;
216: }
217:
218: // End of comment not found, highlight the lines
219: doc.setCharacterAttributes(startDelimiter, offset
220: - startDelimiter + 1, comment, false);
221: return true;
222: }
223:
224: /**
225: * Highlight comment lines to matching end delimiter
226: */
227: private void commentLinesAfter(String content, int line) {
228: int offset = rootElement.getElement(line).getEndOffset();
229:
230: // End of comment not found, nothing to do
231: int endDelimiter = indexOf(content, getEndDelimiter(), offset);
232: if (endDelimiter < 0) {
233: return;
234: }
235:
236: // Matching start/end of comment found, comment the lines
237: int startDelimiter = lastIndexOf(content, getStartDelimiter(),
238: endDelimiter);
239: if (startDelimiter < 0 || startDelimiter <= offset) {
240: doc.setCharacterAttributes(offset, endDelimiter - offset
241: + 1, comment, false);
242: }
243: }
244:
245: /**
246: * Highlight lines to start or end delimiter
247: */
248: private void highlightLinesAfter(String content, int line)
249: throws BadLocationException {
250: int offset = rootElement.getElement(line).getEndOffset();
251:
252: // Start/End delimiter not found, nothing to do
253: int startDelimiter = indexOf(content, getStartDelimiter(),
254: offset);
255: int endDelimiter = indexOf(content, getEndDelimiter(), offset);
256:
257: if (startDelimiter < 0) {
258: startDelimiter = content.length();
259: }
260:
261: if (endDelimiter < 0) {
262: endDelimiter = content.length();
263: }
264:
265: int delimiter = Math.min(startDelimiter, endDelimiter);
266: if (delimiter < offset) {
267: return;
268: }
269:
270: // Start/End delimiter found, reapply highlighting
271: int endLine = rootElement.getElementIndex(delimiter);
272: for (int i = line + 1; i < endLine; i++) {
273: Element branch = rootElement.getElement(i);
274: Element leaf = doc.getCharacterElement(branch
275: .getStartOffset());
276: AttributeSet as = leaf.getAttributes();
277:
278: if (as.isEqual(comment)) {
279: applyHighlighting(content, i);
280: }
281: }
282: }
283:
284: /**
285: * Parse the line to determine the appropriate highlighting
286: */
287: private void applyHighlighting(String content, int line)
288: throws BadLocationException {
289: int startOffset = rootElement.getElement(line).getStartOffset();
290: int endOffset = rootElement.getElement(line).getEndOffset() - 1;
291:
292: int lineLength = endOffset - startOffset;
293: int contentLength = content.length();
294:
295: if (endOffset >= contentLength) {
296: endOffset = contentLength - 1;
297: }
298:
299: // check for multi line comments
300: // (always set the comment attribute for the entire line)
301: if (endingMultiLineComment(content, startOffset, endOffset)
302: || isMultiLineComment()
303: || startingMultiLineComment(content, startOffset,
304: endOffset)) {
305: doc.setCharacterAttributes(startOffset, endOffset
306: - startOffset + 1, comment, false);
307: return;
308: }
309:
310: // set normal attributes for the line
311: doc.setCharacterAttributes(startOffset, lineLength, normal,
312: true);
313:
314: // check for single line comment
315: int index = content.indexOf(getSingleLineDelimiter(),
316: startOffset);
317: if ((index > -1) && (index < endOffset)) {
318: doc.setCharacterAttributes(index, endOffset - index + 1,
319: comment, false);
320: endOffset = index - 1;
321: }
322:
323: // check for tokens
324: checkForTokens(content, startOffset, endOffset);
325: }
326:
327: /*
328: * Does this line contain the start delimiter
329: */
330: private boolean startingMultiLineComment(String content,
331: int startOffset, int endOffset) throws BadLocationException {
332: int index = indexOf(content, getStartDelimiter(), startOffset);
333:
334: if ((index < 0) || (index > endOffset)) {
335: return false;
336: } else {
337: setMultiLineComment(true);
338: return true;
339: }
340: }
341:
342: /*
343: * Does this line contain the end delimiter
344: */
345: private boolean endingMultiLineComment(String content,
346: int startOffset, int endOffset) throws BadLocationException {
347: int index = indexOf(content, getEndDelimiter(), startOffset);
348:
349: if ((index < 0) || (index > endOffset)) {
350: return false;
351: } else {
352: setMultiLineComment(false);
353: return true;
354: }
355: }
356:
357: /*
358: * We have found a start delimiter
359: * and are still searching for the end delimiter
360: */
361: private boolean isMultiLineComment() {
362: return multiLineComment;
363: }
364:
365: private void setMultiLineComment(boolean value) {
366: multiLineComment = value;
367: }
368:
369: /*
370: * Parse the line for tokens to highlight
371: */
372: private void checkForTokens(String content, int startOffset,
373: int endOffset) {
374: while (startOffset <= endOffset) {
375: // skip the delimiters to find the start of a new token
376: while (isDelimiter(content.substring(startOffset,
377: startOffset + 1))) {
378: if (startOffset < endOffset) {
379: startOffset++;
380: } else {
381: return;
382: }
383: }
384:
385: // Extract and process the entire token
386: if (isQuoteDelimiter(content.substring(startOffset,
387: startOffset + 1))) {
388: startOffset = getQuoteToken(content, startOffset,
389: endOffset);
390: } else {
391: startOffset = getOtherToken(content, startOffset,
392: endOffset);
393: }
394: }
395: }
396:
397: /*
398: *
399: */
400: private int getQuoteToken(String content, int startOffset,
401: int endOffset) {
402: String quoteDelimiter = content.substring(startOffset,
403: startOffset + 1);
404: String escapeString = getEscapeString(quoteDelimiter);
405:
406: int index;
407: int endOfQuote = startOffset;
408: // skip over the escape quotes in this quote
409: index = content.indexOf(escapeString, endOfQuote + 1);
410: while ((index > -1) && (index < endOffset)) {
411: endOfQuote = index + 1;
412: index = content.indexOf(escapeString, endOfQuote);
413: }
414:
415: // now find the matching delimiter
416: index = content.indexOf(quoteDelimiter, endOfQuote + 1);
417: if ((index < 0) || (index > endOffset)) {
418: endOfQuote = endOffset;
419: } else {
420: endOfQuote = index;
421: }
422: doc.setCharacterAttributes(startOffset, endOfQuote
423: - startOffset + 1, quote, false);
424: return endOfQuote + 1;
425: }
426:
427: /*
428: *
429: */
430: private int getOtherToken(String content, int startOffset,
431: int endOffset) {
432: int endOfToken = startOffset + 1;
433: while (endOfToken <= endOffset) {
434: if (isDelimiter(content.substring(endOfToken,
435: endOfToken + 1))) {
436: break;
437: }
438:
439: endOfToken++;
440: }
441: String token = content.substring(startOffset, endOfToken);
442:
443: if (isKeyword(token)) {
444: doc.setCharacterAttributes(startOffset, endOfToken
445: - startOffset, keyword, false);
446: }
447: return endOfToken + 1;
448: }
449:
450: /*
451: * Assume the needle will the found at the start/end of the line
452: */
453: private int indexOf(String content, String needle, int offset) {
454: int index;
455:
456: while ((index = content.indexOf(needle, offset)) != -1) {
457: String text = getLine(content, index).trim();
458:
459: if (text.startsWith(needle) || text.endsWith(needle)) {
460: break;
461: } else {
462: offset = index + 1;
463: }
464: }
465:
466: return index;
467: }
468:
469: /*
470: * Assume the needle will the found at the start/end of the line
471: */
472: private int lastIndexOf(String content, String needle, int offset) {
473: int index;
474:
475: while ((index = content.lastIndexOf(needle, offset)) != -1) {
476: String text = getLine(content, index).trim();
477:
478: if (text.startsWith(needle) || text.endsWith(needle)) {
479: break;
480: } else {
481: offset = index - 1;
482: }
483: }
484:
485: return index;
486: }
487:
488: private String getLine(String content, int offset) {
489: int line = rootElement.getElementIndex(offset);
490: Element lineElement = rootElement.getElement(line);
491: int start = lineElement.getStartOffset();
492: int end = lineElement.getEndOffset();
493: return content.substring(start, end - 1);
494: }
495:
496: /*
497: * Override for other languages
498: */
499: protected boolean isDelimiter(String character) {
500: String operands = ";:{}()[]+-/%<=>!&|^~*";
501:
502: if (Character.isWhitespace(character.charAt(0))
503: || operands.indexOf(character) != -1) {
504: return true;
505: } else {
506: return false;
507: }
508: }
509:
510: /*
511: * Override for other languages
512: */
513: protected boolean isQuoteDelimiter(String character) {
514: String quoteDelimiters = "\"'";
515:
516: if (quoteDelimiters.indexOf(character) < 0) {
517: return false;
518: } else {
519: return true;
520: }
521: }
522:
523: /*
524: * Override for other languages
525: */
526: protected boolean isKeyword(String token) {
527: if (keywords.contains(token)) {
528: return keywords.contains(token);
529: } else {
530: return keywords.contains(token.toUpperCase());
531: }
532: }
533:
534: /*
535: * Override for other languages
536: */
537: protected String getStartDelimiter() {
538: // return "/*";
539: return "--";
540: }
541:
542: /*
543: * Override for other languages
544: */
545: protected String getEndDelimiter() {
546: // return "*/";
547: return "--";
548: }
549:
550: /*
551: * Override for other languages
552: */
553: protected String getSingleLineDelimiter() {
554: return "//";
555: }
556:
557: /*
558: * Override for other languages
559: */
560: protected String getEscapeString(String quoteDelimiter) {
561: return "\\" + quoteDelimiter;
562: }
563:
564: protected String addMatchingBrace(int offset)
565: throws BadLocationException {
566: StringBuffer whiteSpace = new StringBuffer();
567: int line = rootElement.getElementIndex(offset);
568: int i = rootElement.getElement(line).getStartOffset();
569:
570: while (true) {
571: String temp = doc.getText(i, 1);
572:
573: if (temp.equals(" ") || temp.equals("\t")) {
574: whiteSpace.append(temp);
575: i++;
576: } else {
577: break;
578: }
579: }
580:
581: return "(\t)";
582: }
583: }
|