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-2006 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:
042: package org.netbeans.editor.ext;
043:
044: import java.util.Map;
045: import java.util.HashMap;
046: import javax.swing.event.DocumentListener;
047: import javax.swing.event.DocumentEvent;
048: import javax.swing.text.BadLocationException;
049: import javax.swing.text.JTextComponent;
050: import org.netbeans.editor.BaseDocument;
051: import org.netbeans.editor.SyntaxSupport;
052: import org.netbeans.editor.Utilities;
053: import org.netbeans.editor.TokenProcessor;
054: import org.netbeans.editor.TokenID;
055: import org.netbeans.editor.TokenContextPath;
056: import org.netbeans.editor.TokenItem;
057: import org.netbeans.editor.SettingsNames;
058: import org.netbeans.editor.FinderFactory;
059: import org.netbeans.editor.TextBatchProcessor;
060: import org.netbeans.editor.Analyzer;
061:
062: /**
063: * Support methods for syntax analyzes
064: *
065: * @author Miloslav Metelka
066: * @version 1.00
067: */
068:
069: public class ExtSyntaxSupport extends SyntaxSupport {
070:
071: // used in ExtKit
072:
073: /** Schedule content update making completion visible. */
074: public static final int COMPLETION_POPUP = 0;
075: /** Cancel request without changing completion visibility. */
076: public static final int COMPLETION_CANCEL = 1;
077: /** Update content immediatelly if it's currently visible. */
078: public static final int COMPLETION_REFRESH = 2;
079: /** Schedule content update if it's currently visible. */
080: public static final int COMPLETION_POST_REFRESH = 3;
081: /** Hide completion. */
082: public static final int COMPLETION_HIDE = 4;
083:
084: private static final TokenID[] EMPTY_TOKEN_ID_ARRAY = new TokenID[0];
085:
086: /** Listens for the changes on the document. Children can override
087: * the documentModified() method to perform some processing.
088: */
089: private DocumentListener docL;
090:
091: /** Map holding the [position, local-variable-map] pairs */
092: private HashMap localVarMaps = new HashMap();
093:
094: /** Map holding the [position, global-variable-map] pairs */
095: private HashMap globalVarMaps = new HashMap();
096:
097: public ExtSyntaxSupport(BaseDocument doc) {
098: super (doc);
099:
100: // Create listener to listen on document changes
101: docL = new DocumentListener() {
102: public void insertUpdate(DocumentEvent evt) {
103: documentModified(evt);
104: }
105:
106: public void removeUpdate(DocumentEvent evt) {
107: documentModified(evt);
108: }
109:
110: public void changedUpdate(DocumentEvent evt) {
111: }
112: };
113: getDocument().addDocumentListener(docL);
114: }
115:
116: /** Get the chain of the tokens for the given block of text.
117: * The returned chain of token-items reflects the tokens
118: * as they occur in the text and therefore the first token
119: * can start at the slightly lower position than the requested one.
120: * The chain itself can be extended automatically when
121: * reaching the first chain item and calling <tt>getPrevious()</tt>
122: * on it. Another chunk of the tokens will be parsed and
123: * the head of the chain will be extended. However this happens
124: * only in case there was no modification performed to the document
125: * between the creation of the chain and this moment. Otherwise
126: * this call throws <tt>IllegalStateException</tt>.
127: *
128: * @param startOffset starting position of the block
129: * @param endOffset ending position of the block
130: * @return the first item of the token-item chain or null if there are
131: * no tokens in the given area or the area is so small that it lays
132: * inside one token. To prevent this provide the area that spans a new-line.
133: */
134: public TokenItem getTokenChain(int startOffset, int endOffset)
135: throws BadLocationException {
136:
137: if (startOffset < 0) {
138: throw new IllegalArgumentException("startOffset="
139: + startOffset + " < 0"); // NOI18N
140: }
141: if (startOffset > endOffset) {
142: throw new IllegalArgumentException("startOffset="
143: + startOffset // NOI18N
144: + " > endOffset=" + endOffset); // NOI18N
145: }
146: TokenItem chain = null;
147: BaseDocument doc = getDocument();
148: doc.readLock();
149: try {
150: int docLen = doc.getLength();
151: endOffset = Math.min(endOffset, docLen);
152: if (startOffset < docLen) {
153: TokenItemTP tp = new TokenItemTP();
154: tp.targetOffset = endOffset;
155: tokenizeText(tp, startOffset, endOffset, false);
156: chain = tp.getTokenChain();
157: }
158: } finally {
159: doc.readUnlock();
160: }
161:
162: return chain;
163: }
164:
165: /** Called when the document was modified by either the insert or removal.
166: * @param evt event received with the modification notification. getType()
167: * can be used to obtain the type of the event.
168: */
169: protected void documentModified(DocumentEvent evt) {
170: // Invalidate variable maps
171: if (localVarMaps.size() > 0)
172: localVarMaps.clear();
173: if (globalVarMaps.size() > 0)
174: globalVarMaps.clear();
175: }
176:
177: /** Get the bracket finder that will search for the matching bracket
178: * or null if the bracket character doesn't belong to bracket
179: * characters.
180: */
181: protected BracketFinder getMatchingBracketFinder(char bracketChar) {
182: BracketFinder bf = new BracketFinder(bracketChar);
183: if (bf.moveCount == 0) { // not valid bracket char
184: bf = null;
185: }
186:
187: return bf;
188: }
189:
190: /** Find matching bracket or more generally block
191: * that matches with the current position.
192: * @param offset position of the starting bracket
193: * @param simple whether the search should skip comment and possibly other areas.
194: * This can be useful when the speed is critical, because the simple
195: * search is faster.
196: * @return array of integers containing starting and ending position
197: * of the block in the document. Null is returned if there's
198: * no matching block.
199: */
200: public int[] findMatchingBlock(int offset, boolean simpleSearch)
201: throws BadLocationException {
202: char bracketChar = getDocument().getChars(offset, 1)[0];
203: int foundPos = -1;
204:
205: final BracketFinder bf = getMatchingBracketFinder(bracketChar);
206:
207: if (bf != null) { // valid finder
208: if (!simpleSearch) {
209: TokenID tokenID = getTokenID(offset);
210: TokenID[] bst = getBracketSkipTokens();
211: for (int i = bst.length - 1; i >= 0; i--) {
212: if (tokenID == bst[i]) {
213: simpleSearch = true; // turn to simple search
214: break;
215: }
216: }
217: }
218:
219: if (simpleSearch) { // don't exclude comments etc.
220: if (bf.isForward()) {
221: foundPos = getDocument().find(bf, offset, -1);
222: } else {
223: foundPos = getDocument().find(bf, offset + 1, 0);
224: }
225:
226: } else { // exclude comments etc. from the search
227: TextBatchProcessor tbp = new TextBatchProcessor() {
228: public int processTextBatch(BaseDocument doc,
229: int startPos, int endPos, boolean lastBatch) {
230: try {
231: int[] blks = getTokenBlocks(startPos,
232: endPos, getBracketSkipTokens());
233: return findOutsideBlocks(bf, startPos,
234: endPos, blks);
235: } catch (BadLocationException e) {
236: return -1;
237: }
238: }
239: };
240:
241: if (bf.isForward()) {
242: foundPos = getDocument().processText(tbp, offset,
243: -1);
244: } else {
245: foundPos = getDocument().processText(tbp,
246: offset + 1, 0);
247: }
248: }
249: }
250:
251: return (foundPos != -1) ? new int[] { foundPos, foundPos + 1 }
252: : null;
253: }
254:
255: /** Get the array of token IDs that should be skipped when
256: * searching for matching bracket. It usually includes comments
257: * and character and string constants. Returns empty array by default.
258: */
259: protected TokenID[] getBracketSkipTokens() {
260: return EMPTY_TOKEN_ID_ARRAY;
261: }
262:
263: /** Gets the token-id of the token at the given position.
264: * @param offset position at which the token should be returned
265: * @return token-id of the token at the requested position. If there's no more
266: * tokens in the text, the <tt>Syntax.INVALID</tt> is returned.
267: */
268: public TokenID getTokenID(int offset) throws BadLocationException {
269: FirstTokenTP fttp = new FirstTokenTP();
270: tokenizeText(fttp, offset, getDocument().getLength(), true);
271: return fttp.getTokenID();
272: }
273:
274: /** Is the identifier at the position a function call?
275: * It first checks whether there is a identifier under
276: * the cursor and then it searches for the function call
277: * character - usually '('. Note: Java 1.5 annotations are not
278: * taken as function calls.
279: * @param identifierBlock int[2] block delimiting the identifier
280: * @return int[2] block or null if there's no function call
281: */
282: public int[] getFunctionBlock(int[] identifierBlock)
283: throws BadLocationException {
284: if (identifierBlock != null) {
285: int nwPos = Utilities.getFirstNonWhiteFwd(getDocument(),
286: identifierBlock[1]);
287: if ((nwPos >= 0)
288: && (getDocument().getChars(nwPos, 1)[0] == '(')) {
289: return new int[] { identifierBlock[0], nwPos + 1 };
290: }
291: }
292: return null;
293: }
294:
295: public int[] getFunctionBlock(int offset)
296: throws BadLocationException {
297: return getFunctionBlock(Utilities.getIdentifierBlock(
298: getDocument(), offset));
299: }
300:
301: public boolean isWhitespaceToken(TokenID tokenID, char[] buffer,
302: int offset, int tokenLength) {
303: return Analyzer.isWhitespace(buffer, offset, tokenLength);
304: }
305:
306: public boolean isCommentOrWhitespace(int startPos, int endPos)
307: throws BadLocationException {
308: CommentOrWhitespaceTP tp = new CommentOrWhitespaceTP(
309: getCommentTokens());
310: tokenizeText(tp, startPos, endPos, true);
311: return !tp.nonEmpty;
312: }
313:
314: /** Gets the last non-blank and non-comment character on the given line.
315: */
316: public int getRowLastValidChar(int offset)
317: throws BadLocationException {
318: return Utilities.getRowLastNonWhite(getDocument(), offset);
319: }
320:
321: /** Does the line contain some valid code besides of possible white space
322: * and comments?
323: */
324: public boolean isRowValid(int offset) throws BadLocationException {
325: return Utilities.isRowWhite(getDocument(), offset);
326: }
327:
328: /** Get the array of token IDs that denote the comments.
329: * Returns empty array by default.
330: */
331: public TokenID[] getCommentTokens() {
332: return EMPTY_TOKEN_ID_ARRAY;
333: }
334:
335: /** Get the blocks consisting of comments in a specified document area.
336: * @param doc document to work with
337: * @param startPos starting position of the searched document area
338: * @param endPos ending position of the searched document area
339: */
340: public int[] getCommentBlocks(int startPos, int endPos)
341: throws BadLocationException {
342: return getTokenBlocks(startPos, endPos, getCommentTokens());
343: }
344:
345: /** Find the type of the variable. The default behavior is to first
346: * search for the local variable declaration and then possibly for
347: * the global declaration and if the declaration position is found
348: * to get the first word on that position.
349: * @return it returns Object to enable the custom implementations
350: * to return the appropriate instances.
351: */
352: public Object findType(String varName, int varPos) {
353: Object type = null;
354: Map varMap = getLocalVariableMap(varPos); // first try local vars
355: if (varMap != null) {
356: type = varMap.get(varName);
357: }
358:
359: if (type == null) {
360: varMap = getGlobalVariableMap(varPos); // try global vars
361: if (varMap != null) {
362: type = varMap.get(varName);
363: }
364: }
365:
366: return type;
367: }
368:
369: public Map getLocalVariableMap(int offset) {
370: Integer posI = new Integer(offset);
371: Map varMap = (Map) localVarMaps.get(posI);
372: if (varMap == null) {
373: varMap = buildLocalVariableMap(offset);
374: localVarMaps.put(posI, varMap);
375: }
376: return varMap;
377: }
378:
379: protected Map buildLocalVariableMap(int offset) {
380: int methodStartPos = getMethodStartPosition(offset);
381: if (methodStartPos >= 0 && methodStartPos < offset) {
382: VariableMapTokenProcessor vmtp = createVariableMapTokenProcessor(
383: methodStartPos, offset);
384: try {
385: tokenizeText(vmtp, methodStartPos, offset, true);
386: return vmtp.getVariableMap();
387: } catch (BadLocationException e) {
388: // will default null
389: }
390: }
391: return null;
392: }
393:
394: public Map getGlobalVariableMap(int offset) {
395: Integer posI = new Integer(offset);
396: Map varMap = (Map) globalVarMaps.get(posI);
397: if (varMap == null) {
398: varMap = buildGlobalVariableMap(offset);
399: globalVarMaps.put(posI, varMap);
400: }
401: return varMap;
402: }
403:
404: protected Map buildGlobalVariableMap(int offset) {
405: int docLen = getDocument().getLength();
406: VariableMapTokenProcessor vmtp = createVariableMapTokenProcessor(
407: 0, docLen);
408: if (vmtp != null) {
409: try {
410: tokenizeText(vmtp, 0, docLen, true);
411: return vmtp.getVariableMap();
412: } catch (BadLocationException e) {
413: // will default null
414: }
415: }
416: return null;
417: }
418:
419: /** Get the start position of the method or the area
420: * where the declaration can start.
421: */
422: protected int getMethodStartPosition(int offset) {
423: return 0; // return begining of the document by default
424: }
425:
426: /** Find either the local or global declaration position. First
427: * try the local declaration and if it doesn't succeed, then
428: * try the global declaration.
429: */
430: public int findDeclarationPosition(String varName, int varPos) {
431: int offset = findLocalDeclarationPosition(varName, varPos);
432: if (offset < 0) {
433: offset = findGlobalDeclarationPosition(varName, varPos);
434: }
435: return offset;
436: }
437:
438: public int findLocalDeclarationPosition(String varName, int varPos) {
439: int methodStartPos = getMethodStartPosition(varPos);
440: if (methodStartPos >= 0 && methodStartPos < varPos) {
441: return findDeclarationPositionImpl(varName, methodStartPos,
442: varPos);
443: }
444: return -1;
445: }
446:
447: /** Get the position of the global declaration of a given variable.
448: * By default it's implemented to use the same token processor as for the local
449: * variables but the whole file is searched.
450: */
451: public int findGlobalDeclarationPosition(String varName, int varPos) {
452: return findDeclarationPositionImpl(varName, 0, getDocument()
453: .getLength());
454: }
455:
456: private int findDeclarationPositionImpl(String varName,
457: int startPos, int endPos) {
458: DeclarationTokenProcessor dtp = createDeclarationTokenProcessor(
459: varName, startPos, endPos);
460: if (dtp != null) {
461: try {
462: tokenizeText(dtp, startPos, endPos, true);
463: return dtp.getDeclarationPosition();
464: } catch (BadLocationException e) {
465: // will default to -1
466: }
467: }
468: return -1;
469: }
470:
471: protected DeclarationTokenProcessor createDeclarationTokenProcessor(
472: String varName, int startPos, int endPos) {
473: return null;
474: }
475:
476: protected VariableMapTokenProcessor createVariableMapTokenProcessor(
477: int startPos, int endPos) {
478: return null;
479: }
480:
481: /** Check and possibly popup, hide or refresh the completion */
482: public int checkCompletion(JTextComponent target, String typedText,
483: boolean visible) {
484: return visible ? COMPLETION_HIDE : COMPLETION_CANCEL;
485: }
486:
487: /** Token processor extended to get declaration position
488: * of the given variable.
489: */
490: public interface DeclarationTokenProcessor extends TokenProcessor {
491:
492: /** Get the declaration position. */
493: public int getDeclarationPosition();
494:
495: }
496:
497: public interface VariableMapTokenProcessor extends TokenProcessor {
498:
499: /** Get the map that contains the pairs [variable-name, variable-type]. */
500: public Map getVariableMap();
501:
502: }
503:
504: /** Finder for the matching bracket. It gets the original bracket char
505: * and searches for the appropriate matching bracket character.
506: */
507: public class BracketFinder extends FinderFactory.GenericFinder {
508:
509: /** Original bracket char */
510: protected char bracketChar;
511:
512: /** Matching bracket char */
513: protected char matchChar;
514:
515: /** Depth of original brackets */
516: private int depth;
517:
518: /** Will it be a forward finder +1 or backward finder -1 or 0 when
519: * the given character is not bracket character.
520: */
521: protected int moveCount;
522:
523: /**
524: * @param bracketChar bracket char
525: */
526: public BracketFinder(char bracketChar) {
527: this .bracketChar = bracketChar;
528:
529: updateStatus();
530:
531: forward = (moveCount > 0);
532: }
533:
534: /** Check whether the bracketChar really contains
535: * the bracket character. If so assign the matchChar
536: * and moveCount variables.
537: */
538: protected boolean updateStatus() {
539: boolean valid = true;
540: switch (bracketChar) {
541: case '(':
542: matchChar = ')';
543: moveCount = +1;
544: break;
545: case ')':
546: matchChar = '(';
547: moveCount = -1;
548: break;
549: case '{':
550: matchChar = '}';
551: moveCount = +1;
552: break;
553: case '}':
554: matchChar = '{';
555: moveCount = -1;
556: break;
557: case '[':
558: matchChar = ']';
559: moveCount = +1;
560: break;
561: case ']':
562: matchChar = '[';
563: moveCount = -1;
564: break;
565: case '<':
566: matchChar = '>';
567: moveCount = +1;
568: break;
569: case '>':
570: matchChar = '<';
571: moveCount = -1;
572: break;
573: default:
574: valid = false;
575: }
576: return valid;
577: }
578:
579: protected int scan(char ch, boolean lastChar) {
580: if (ch == bracketChar) {
581: depth++;
582: } else if (ch == matchChar) {
583: if (--depth == 0) {
584: found = true;
585: return 0;
586: }
587: }
588: return moveCount;
589: }
590:
591: }
592:
593: /** Create token-items */
594: final class TokenItemTP implements TokenProcessor {
595:
596: private Item firstItem;
597:
598: private Item lastItem;
599:
600: private int fwdBatchLineCnt;
601: private int bwdBatchLineCnt;
602:
603: private char[] buffer;
604:
605: private int bufferStartPos;
606:
607: /** Target position corresponding to the begining of the token
608: * that is already chained if searching for backward tokens,
609: * or, the last token that should be scanned if searching
610: * in forward direction.
611: */
612: int targetOffset;
613:
614: TokenItemTP() {
615: fwdBatchLineCnt = bwdBatchLineCnt = ((Integer) getDocument()
616: .getProperty(SettingsNames.LINE_BATCH_SIZE))
617: .intValue();
618: }
619:
620: public TokenItem getTokenChain() {
621: return firstItem;
622: }
623:
624: public boolean token(TokenID tokenID,
625: TokenContextPath tokenContextPath,
626: int tokenBufferOffset, int tokenLength) {
627: if (bufferStartPos + tokenBufferOffset >= targetOffset) { // stop scanning
628: return false;
629: }
630:
631: lastItem = new Item(tokenID, tokenContextPath,
632: bufferStartPos + tokenBufferOffset, new String(
633: buffer, tokenBufferOffset, tokenLength),
634: lastItem);
635:
636: if (firstItem == null) { // not yet assigned
637: firstItem = lastItem;
638: }
639:
640: return true;
641: }
642:
643: public int eot(int offset) {
644: return ((Integer) getDocument().getProperty(
645: SettingsNames.MARK_DISTANCE)).intValue();
646: }
647:
648: public void nextBuffer(char[] buffer, int offset, int len,
649: int startPos, int preScan, boolean lastBuffer) {
650: this .buffer = buffer;
651: bufferStartPos = startPos - offset;
652: }
653:
654: Item getNextChunk(Item i) {
655: BaseDocument doc = getDocument();
656: int itemEndPos = i.getOffset() + i.getImage().length();
657: int docLen = doc.getLength();
658: if (itemEndPos == docLen) {
659: return null;
660: }
661:
662: int endPos;
663: try {
664: endPos = Utilities.getRowStart(doc, itemEndPos,
665: fwdBatchLineCnt);
666: } catch (BadLocationException e) {
667: return null;
668: }
669:
670: if (endPos == -1) { // past end of doc
671: endPos = docLen;
672: }
673: fwdBatchLineCnt *= 2; // larger batch in next call
674:
675: Item nextChunkHead = null;
676: Item fit = firstItem;
677: Item lit = lastItem;
678: try {
679: // Simulate initial conditions
680: firstItem = null;
681: lastItem = null;
682: targetOffset = endPos;
683:
684: tokenizeText(this , itemEndPos, endPos, false);
685: nextChunkHead = firstItem;
686:
687: } catch (BadLocationException e) {
688: } finally {
689: // Link previous last with the current first
690: if (firstItem != null) {
691: lit.next = firstItem;
692: firstItem.previous = lit;
693: }
694:
695: firstItem = fit;
696: if (lastItem == null) { // restore in case of no token or crash
697: lastItem = lit;
698: }
699: }
700:
701: return nextChunkHead;
702: }
703:
704: Item getPreviousChunk(Item i) {
705: BaseDocument doc = getDocument();
706: int itemStartPos = i.getOffset();
707: if (itemStartPos == 0) {
708: return null;
709: }
710:
711: int startPos;
712: try {
713: startPos = Utilities.getRowStart(doc, itemStartPos,
714: -bwdBatchLineCnt);
715: } catch (BadLocationException e) {
716: return null;
717: }
718:
719: if (startPos == -1) { // before begining of doc
720: startPos = 0;
721: }
722: bwdBatchLineCnt *= 2;
723:
724: Item previousChunkLast = null;
725: Item fit = firstItem;
726: Item lit = lastItem;
727: try {
728: // Simulate initial conditions
729: firstItem = null;
730: lastItem = null;
731: targetOffset = itemStartPos;
732:
733: tokenizeText(this , startPos, itemStartPos, false);
734: previousChunkLast = lastItem;
735:
736: } catch (BadLocationException e) {
737: } finally {
738: // Link previous last
739: if (lastItem != null) {
740: fit.previous = lastItem;
741: lastItem.next = fit;
742: }
743:
744: lastItem = lit;
745: if (firstItem == null) { // restore in case of no token or crash
746: firstItem = fit;
747: }
748: }
749:
750: return previousChunkLast;
751: }
752:
753: final class Item extends TokenItem.AbstractItem {
754:
755: Item previous;
756:
757: TokenItem next;
758:
759: Item(TokenID tokenID, TokenContextPath tokenContextPath,
760: int offset, String image, Item previous) {
761: super (tokenID, tokenContextPath, offset, image);
762: if (previous != null) {
763: this .previous = previous;
764: previous.next = this ;
765: }
766: }
767:
768: public TokenItem getNext() {
769: if (next == null) {
770: next = getNextChunk(this );
771: }
772: return next;
773: }
774:
775: public TokenItem getPrevious() {
776: if (previous == null) {
777: previous = getPreviousChunk(this );
778: }
779: return previous;
780: }
781:
782: }
783:
784: }
785:
786: /** Token processor that matches either the comments or whitespace */
787: class CommentOrWhitespaceTP implements TokenProcessor {
788:
789: private char[] buffer;
790:
791: private TokenID[] commentTokens;
792:
793: boolean nonEmpty;
794:
795: CommentOrWhitespaceTP(TokenID[] commentTokens) {
796: this .commentTokens = commentTokens;
797: }
798:
799: public boolean token(TokenID tokenID,
800: TokenContextPath tokenContextPath, int offset,
801: int tokenLength) {
802: for (int i = 0; i < commentTokens.length; i++) {
803: if (tokenID == commentTokens[i]) {
804: return true; // comment token found
805: }
806: }
807: boolean nonWS = isWhitespaceToken(tokenID, buffer, offset,
808: tokenLength);
809: if (nonWS) {
810: nonEmpty = true;
811: }
812: return nonWS;
813: }
814:
815: public int eot(int offset) {
816: return 0;
817: }
818:
819: public void nextBuffer(char[] buffer, int offset, int len,
820: int startPos, int preScan, boolean lastBuffer) {
821: this .buffer = buffer;
822: }
823:
824: }
825:
826: static class FirstTokenTP implements TokenProcessor {
827:
828: private TokenID tokenID;
829:
830: public TokenID getTokenID() {
831: return tokenID;
832: }
833:
834: public boolean token(TokenID tokenID,
835: TokenContextPath tokenContextPath, int offset,
836: int tokenLen) {
837: this .tokenID = tokenID;
838: return false; // no more tokens
839: }
840:
841: public int eot(int offset) {
842: return 0;
843: }
844:
845: public void nextBuffer(char[] buffer, int offset, int len,
846: int startPos, int preScan, boolean lastBuffer) {
847: }
848:
849: }
850:
851: }
|