001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor;
015:
016: import java.util.HashMap;
017:
018: import javax.swing.text.BadLocationException;
019:
020: /**
021: * Support methods for syntax analyzes
022: *
023: * @author Miloslav Metelka
024: * @version 1.00
025: */
026:
027: public class SyntaxSupport {
028:
029: private static final int[] EMPTY_INT_ARRAY = new int[0];
030:
031: private static final int MATCH_ARRAY_CACHE_SIZE = 3;
032:
033: private HashMap supMap;
034:
035: /** Document to work with */
036: private BaseDocument doc;
037:
038: /**
039: * Whether all the token-ids this class deals with have valid numeric-ids.
040: * It's not necessary to set this flag, however it presents an optimization
041: * in testing whether a token belongs to some group of tokens or not. The
042: * testing whether the particular token belongs to some group is improved by
043: * creating a boolean array in which the numeric-ids serve as the array
044: * indexes.
045: */
046: protected boolean tokenNumericIDsValid;
047:
048: private int[] tokenBlocks = EMPTY_INT_ARRAY;
049:
050: private TokenID[][] lastTokenIDArrays = new TokenID[MATCH_ARRAY_CACHE_SIZE][];
051:
052: private boolean[][] lastMatchArrays = new boolean[MATCH_ARRAY_CACHE_SIZE][];
053:
054: public SyntaxSupport(BaseDocument doc) {
055: this .doc = doc;
056:
057: }
058:
059: /** Getter for the document that this support is associated to. */
060: public final BaseDocument getDocument() {
061: return doc;
062: }
063:
064: /**
065: * Get the support that fits the requested support class in the best way.
066: * The value returned will be either instance of the requested class or its
067: * descendant or it will be null.
068: *
069: * @param syntaxSupportClass
070: * returned value will be instance of this class (or its
071: * descendant) or it will be null
072: * @return instance of syntaxSupportClass (or its descendant) or null if
073: * there's no fitting support.
074: */
075: public synchronized SyntaxSupport get(Class syntaxSupportClass) {
076: if (supMap == null) {
077: supMap = new HashMap(11);
078: }
079:
080: SyntaxSupport sup = (SyntaxSupport) supMap
081: .get(syntaxSupportClass);
082: if (sup == null) {
083: sup = createSyntaxSupport(syntaxSupportClass);
084: supMap.put(syntaxSupportClass, sup);
085: }
086:
087: return sup;
088: }
089:
090: protected SyntaxSupport createSyntaxSupport(Class syntaxSupportClass) {
091: if (syntaxSupportClass.isInstance(this )) {
092: return this ;
093: }
094: return null;
095: }
096:
097: /**
098: * Get the array of booleans with trues at indexes retrieved as numeric-ids
099: * from the token-id array.
100: */
101: private boolean[] getMatchArray(TokenID[] tokenIDArray) {
102: boolean[] matchArray = null;
103: int ind;
104: for (ind = 0; ind < MATCH_ARRAY_CACHE_SIZE; ind++) {
105: // Test only on array equality, not Arrays.equals(Ob1[], Ob2[])
106: // Supposing they will be static
107: if (tokenIDArray == lastTokenIDArrays[ind]) {
108: matchArray = lastMatchArrays[ind];
109: break;
110: }
111: }
112:
113: if (matchArray == null) { // not found in cache
114: int maxTokenNumericID = -1;
115: if (tokenIDArray != null) {
116: for (int i = 0; i < tokenIDArray.length; i++) {
117: if (tokenIDArray[i].getNumericID() > maxTokenNumericID) {
118: maxTokenNumericID = tokenIDArray[i]
119: .getNumericID();
120: }
121: }
122: }
123:
124: matchArray = new boolean[maxTokenNumericID + 1];
125: for (int i = 0; i < tokenIDArray.length; i++) {
126: matchArray[tokenIDArray[i].getNumericID()] = true;
127: }
128: }
129:
130: if (ind > 0) {
131: ind = Math.min(ind, MATCH_ARRAY_CACHE_SIZE - 1);
132: System.arraycopy(lastTokenIDArrays, 0, lastTokenIDArrays,
133: 1, ind);
134: System.arraycopy(lastMatchArrays, 0, lastMatchArrays, 1,
135: ind);
136: lastTokenIDArrays[0] = tokenIDArray;
137: lastMatchArrays[0] = matchArray;
138: }
139:
140: return matchArray;
141: }
142:
143: /**
144: * Get position pairs covering the blocks that include only the tokens from
145: * the given token array. Although the startPos can be greater than endPos,
146: * the blocks are always returned in the natural order.
147: *
148: * @param doc
149: * document to work with
150: * @param startPos
151: * starting position of the requested document area.
152: * @param endPos
153: * ending position of the requested document area
154: * @param tokenIDArray
155: * the array of the token IDs that should be in the blocks.
156: */
157: public synchronized int[] getTokenBlocks(int startPos, int endPos,
158: TokenID[] tokenIDArray) throws BadLocationException {
159: doc.readLock();
160: try {
161: boolean matchArray[] = tokenNumericIDsValid ? getMatchArray(tokenIDArray)
162: : null;
163: int blkInd = 0;
164: if (startPos > endPos) { // swap
165: int tmp = startPos;
166: startPos = endPos;
167: endPos = tmp;
168: }
169:
170: SyntaxSeg.Slot slot = SyntaxSeg.getFreeSlot();
171: Syntax syntax = doc.getFreeSyntax();
172: try {
173: doc.op.prepareSyntax(slot, syntax, doc.op
174: .getLeftSyntaxMark(startPos), startPos, endPos
175: - startPos, true);
176:
177: int preScan = syntax.getPreScan();
178: int pos = startPos - preScan;
179: int blkStart = -1;
180:
181: boolean cont = true;
182: while (cont) {
183: TokenID tokenID = syntax.nextToken();
184: if (tokenID == null) {
185: cont = false;
186: } else {
187: // Test whether token-id belongs to the token-array
188: boolean matches = (tokenID != null);
189: if (matches) {
190: if (matchArray != null) {
191: int numID = tokenID.getNumericID();
192: matches = (numID < matchArray.length && matchArray[numID]);
193: } else { // doesn't support numeric-ids
194: matches = false;
195: for (int i = 0; i < tokenIDArray.length; i++) {
196: if (tokenID == tokenIDArray[i]) {
197: matches = true;
198: break;
199: }
200: }
201: }
202: }
203:
204: if (matches) {
205: if (blkStart >= 0) {
206: // still in token block
207: } else {
208: blkStart = Math.max(pos, startPos);
209: }
210: } else { // not searched token
211: if (blkStart >= 0) {
212: tokenBlocks = addTokenBlock(
213: tokenBlocks, blkInd, blkStart,
214: pos);
215: blkInd += 2;
216: blkStart = -1;
217: } else {
218: // still not in block
219: }
220: }
221: pos += syntax.getTokenLength();
222: }
223: }
224:
225: if (blkStart >= 0) { // was in comment
226: tokenBlocks = addTokenBlock(tokenBlocks, blkInd,
227: blkStart, endPos);
228: blkInd += 2;
229: }
230:
231: } finally {
232: doc.releaseSyntax(syntax);
233: SyntaxSeg.releaseSlot(slot);
234: }
235:
236: int[] ret = new int[blkInd];
237: System.arraycopy(tokenBlocks, 0, ret, 0, blkInd);
238: return ret;
239: } finally {
240: doc.readUnlock();
241: }
242: }
243:
244: private int[] addTokenBlock(int[] blks, int blkInd,
245: int blkStartPos, int blkEndPos) {
246: if (blks.length < blkInd + 2) {
247: int[] tmp = new int[Math.max(2, blks.length * 2)];
248: System.arraycopy(blks, 0, tmp, 0, blkInd);
249: blks = tmp;
250: }
251:
252: blks[blkInd++] = blkStartPos;
253: blks[blkInd] = blkEndPos;
254: return blks;
255: }
256:
257: public int findInsideBlocks(Finder finder, int startPos,
258: int endPos, int[] blocks) throws BadLocationException {
259: boolean fwd = (startPos <= endPos);
260:
261: if (fwd) {
262: for (int i = 0; i < blocks.length; i += 2) {
263: int pos = doc.find(finder, blocks[i], blocks[i + 1]);
264: if (pos >= 0) {
265: return pos;
266: }
267: }
268: } else { // find backward
269: for (int i = blocks.length - 2; i >= 0; i -= 2) {
270: int pos = doc.find(finder, blocks[i + 1], blocks[i]);
271: if (pos >= 0) {
272: return pos;
273: }
274: }
275: }
276: return -1;
277: }
278:
279: public int findOutsideBlocks(Finder finder, int startPos,
280: int endPos, int[] blocks) throws BadLocationException {
281: boolean fwd = (startPos <= endPos);
282:
283: if (fwd) {
284: int pos = doc.find(finder, startPos,
285: (blocks.length > 0) ? blocks[0] : endPos);
286: if (pos >= 0) {
287: return pos;
288: }
289:
290: int ind = 2;
291: while (ind <= blocks.length) {
292: pos = doc.find(finder, blocks[ind - 1],
293: (ind >= blocks.length) ? endPos : blocks[ind]);
294: if (pos >= 0) {
295: return pos;
296: }
297: ind += 2;
298: }
299: } else { // find backward
300: int pos = doc.find(finder, startPos,
301: (blocks.length > 0) ? blocks[blocks.length - 1]
302: : endPos);
303: if (pos >= 0) {
304: return pos;
305: }
306:
307: int ind = blocks.length - 2;
308: while (ind >= 0) {
309: pos = doc.find(finder, blocks[ind], (ind == 0) ? endPos
310: : blocks[ind - 1]);
311: if (pos >= 0) {
312: return pos;
313: }
314: ind -= 2;
315: }
316: }
317: return -1;
318: }
319:
320: /**
321: * Initialize the syntax so it's ready to scan the given area.
322: *
323: * @param syntax
324: * lexical analyzer to prepare
325: * @param startPos
326: * starting position of the scanning
327: * @param endPos
328: * ending position of the scanning
329: * @param forceLastBuffer
330: * force the syntax to think that the scanned area is the last in
331: * the document. This is useful for forcing the syntax to process
332: * all the characters in the given area.
333: * @param forceNotLastBuffer
334: * force the syntax to think that the scanned area is NOT the
335: * last buffer in the document. This is useful when the syntax
336: * will continue scanning on another buffer.
337: */
338: public void initSyntax(Syntax syntax, int startPos, int endPos,
339: boolean forceLastBuffer, boolean forceNotLastBuffer)
340: throws BadLocationException {
341: SyntaxSeg.Slot slot = null;
342: doc.readLock();
343: try {
344: slot = SyntaxSeg.getFreeSlot();
345: int docLen = doc.getLength();
346: doc.op.prepareSyntax(slot, syntax, doc.op
347: .getLeftSyntaxMark(startPos), startPos, 0,
348: forceLastBuffer, forceNotLastBuffer);
349: int preScan = syntax.getPreScan();
350: char[] buffer = doc.getChars(startPos - preScan, endPos
351: - startPos + preScan);
352: boolean lastBuffer = forceNotLastBuffer ? false
353: : (forceLastBuffer || (endPos == docLen));
354: syntax.relocate(buffer, preScan, endPos - startPos,
355: lastBuffer, endPos);
356: } finally {
357: if (slot != null) {
358: SyntaxSeg.releaseSlot(slot);
359: }
360: doc.readUnlock();
361: }
362: }
363:
364: /** Check whether the given word is identifier or not. */
365: public boolean isIdentifier(String word) {
366: if (word == null || word.length() == 0) {
367: return false; // not qualified as word
368: }
369:
370: for (int i = 0; i < word.length(); i++) {
371: if (!doc.isIdentifierPart(word.charAt(i))) {
372: return false;
373: }
374: }
375: return true;
376: }
377:
378: /**
379: * Parse the text and pass the resulting tokens to the token processor.
380: *
381: * @param tp
382: * token processor that will be informed about the found tokens.
383: * @param startOffset
384: * starting position in the text
385: * @param endOffset
386: * ending position in the text
387: * @param forceLastBuffer
388: * force the syntax scanner to think that the requested area is
389: * the last in the document.
390: */
391: public void tokenizeText(TokenProcessor tp, int startOffset,
392: int endOffset, boolean forceLastBuffer)
393: throws BadLocationException {
394: SyntaxSeg.Slot slot = null;
395: Syntax syntax = null;
396: doc.readLock();
397: try {
398: slot = SyntaxSeg.getFreeSlot();
399: syntax = doc.getFreeSyntax();
400: int docLen = doc.getLength();
401: doc.op.prepareSyntax(slot, syntax, doc.op
402: .getLeftSyntaxMark(startOffset), startOffset,
403: endOffset - startOffset, forceLastBuffer);
404: int preScan = syntax.getPreScan();
405: tp.nextBuffer(slot.array, syntax.getOffset(), endOffset
406: - startOffset, startOffset, preScan,
407: syntax.lastBuffer);
408:
409: int bufferStartOffset = startOffset - syntax.getOffset();
410:
411: boolean cont = true;
412: while (cont) {
413: TokenID tokenID = syntax.nextToken();
414: TokenContextPath tcp = syntax.getTokenContextPath();
415: if (tokenID == null) { // EOT
416: int nextLen = tp.eot(syntax.tokenOffset);
417: nextLen = Math.min(nextLen, docLen - endOffset);
418: if (nextLen == 0) {
419: cont = false;
420: } else { // continue
421: preScan = syntax.getPreScan();
422: slot.load(doc, endOffset - preScan, preScan
423: + nextLen);
424:
425: boolean lastBuffer = forceLastBuffer
426: || (endOffset + nextLen >= docLen);
427: syntax.relocate(slot.array, slot.offset
428: + preScan, nextLen, lastBuffer,
429: endOffset + nextLen);
430: tp
431: .nextBuffer(slot.array, syntax
432: .getOffset(), nextLen,
433: endOffset, preScan, lastBuffer);
434: bufferStartOffset = endOffset
435: - syntax.getOffset();
436: endOffset += nextLen;
437: }
438:
439: } else { // not EOT
440: int tokenLen = syntax.getTokenLength();
441: int tokenOffset = syntax.getTokenOffset();
442:
443: // Check whether the token isn't too left
444: if (bufferStartOffset + tokenOffset + tokenLen > startOffset) {
445: if (!tp.token(tokenID, tcp, tokenOffset,
446: tokenLen)) {
447: cont = false;
448: }
449: }
450: }
451: }
452: } finally {
453: if (syntax != null) {
454: doc.releaseSyntax(syntax);
455: }
456: if (slot != null) {
457: SyntaxSeg.releaseSlot(slot);
458: }
459: doc.readUnlock();
460: }
461: }
462:
463: /**
464: * Parse the text and pass the resulting tokens to the token processor.
465: *
466: * @param tp
467: * token processor that will be informed about the found tokens.
468: * @param text
469: * text to parse
470: */
471: public void tokenizeText(TokenProcessor tp, String text) {
472: Syntax syntax = null;
473: try {
474: syntax = doc.getFreeSyntax();
475: char[] buf = text.toCharArray();
476: syntax.load(null, buf, 0, buf.length, true, -1);
477:
478: boolean cont = true;
479: while (cont) {
480: TokenID tokenID = syntax.nextToken();
481: TokenContextPath tcp = syntax.getTokenContextPath();
482: if (tokenID == null) {
483: tp.eot(syntax.tokenOffset);
484: cont = false;
485:
486: } else {
487: if (!tp.token(tokenID, tcp,
488: syntax.getTokenOffset(), syntax
489: .getTokenLength())) {
490: cont = false;
491: }
492: }
493: }
494:
495: } finally {
496: if (syntax != null) {
497: doc.releaseSyntax(syntax);
498: }
499: }
500: }
501:
502: /**
503: * Get the member of the chain of the tokens for the given document
504: * position.
505: *
506: * @param offset
507: * position in the document for which the chain is being
508: * retrieved.
509: * @return token-item around the offset or right at the offset. Null is
510: * returned if offset is equal to document length.
511: */
512: public TokenItem getTokenChain(int offset)
513: throws BadLocationException {
514: // null for end of document
515: if (doc.getLength() <= offset) {
516: return null;
517: }
518:
519: return null;
520: }
521:
522: }
|