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